├── .dockerignore
├── .github
└── workflows
│ ├── lint.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .goreleaser.yaml
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── cmd
└── gcp-ip-list
│ └── main.go
├── go.mod
├── go.sum
├── pkg
├── gcp
│ ├── api.go
│ ├── filters.go
│ ├── filters_test.go
│ ├── parsing.go
│ ├── parsing_test.go
│ └── test-data
│ │ └── assets.json
└── output
│ ├── api.go
│ └── api_test.go
└── terraform
├── address.tf
├── cloudsql.tf
├── enable_services.sh
├── kubernetes.tf
├── load-balancer-private.tf
├── load-balancer-public-static.tf
├── load-balancer-public.tf
├── load-balancer-shared.tf
├── locals.tf
├── nat-router.tf
├── network.tf
├── provider.tf
├── vars.tf
└── vm.tf
/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: Linting
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | lint-code:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-go@v5
15 | with:
16 | go-version: '^1.24'
17 |
18 | - name: Lint
19 | uses: golangci/golangci-lint-action@v7
20 | with:
21 | version: v2.0.2
22 |
23 | lint-terraform:
24 | runs-on: ubuntu-latest
25 | steps:
26 | - uses: actions/checkout@v4
27 | - uses: opentofu/setup-opentofu@v1
28 | with:
29 | tofu_version: 1.9
30 |
31 | - name: Format
32 | run: tofu fmt -check
33 | working-directory: ./terraform
34 |
35 | - name: Init
36 | run: tofu init
37 | working-directory: ./terraform
38 |
39 | - name: Validate
40 | run: tofu validate
41 | working-directory: ./terraform
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Releasing
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | permissions:
9 | contents: write
10 | id-token: write
11 | attestations: write
12 |
13 | jobs:
14 | goreleaser:
15 | runs-on: ubuntu-latest
16 | steps:
17 | -
18 | name: Checkout
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 | -
23 | name: Set up Go
24 | uses: actions/setup-go@v5
25 | with:
26 | go-version: '^1.24'
27 |
28 | -
29 | name: Set up cosign
30 | uses: sigstore/cosign-installer@v3.8.1
31 |
32 | -
33 | name: Set up homebrew deploy key
34 | shell: bash
35 | env:
36 | HOMEBREW_GH_DEPLOY_KEY: ${{ secrets.HOMEBREW_GH_DEPLOY_KEY }}
37 | run: |
38 | echo $HOMEBREW_GH_DEPLOY_KEY | base64 -d > /tmp/HOMEBREW_GH_DEPLOY_KEY
39 | chmod 700 /tmp/HOMEBREW_GH_DEPLOY_KEY
40 |
41 | -
42 | name: Run GoReleaser
43 | uses: goreleaser/goreleaser-action@v6
44 | with:
45 | distribution: goreleaser
46 | version: '~> v2'
47 | args: release --clean
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | - uses: actions/attest-build-provenance@v2
51 | with:
52 | subject-path: "dist/*.tar.gz,dist/*.zip,dist/*_checksums.txt"
53 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | run-tests:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/setup-go@v5
15 | with:
16 | go-version: '^1.24'
17 |
18 | - name: Build
19 | run: go build -v github.com/mark-adams/gcp-ip-list/cmd/gcp-ip-list
20 |
21 | - name: Test
22 | run: go test -v ./...
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | terraform/.terraform*
2 | terraform/*.tfstate*
3 | .vscode# Added by goreleaser init:
4 | dist/
5 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
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 | ldflags:
13 | - "-X main.version={{.Version}}"
14 | env:
15 | - CGO_ENABLED=0
16 | goos:
17 | - linux
18 | - windows
19 | - darwin
20 |
21 | archives:
22 | - formats: [tar.gz]
23 | # this name template makes the OS and Arch compatible with the results of `uname`.
24 | name_template: >-
25 | {{ .ProjectName }}_
26 | {{- title .Os }}_
27 | {{- if eq .Arch "amd64" }}x86_64
28 | {{- else if eq .Arch "386" }}i386
29 | {{- else }}{{ .Arch }}{{ end }}
30 | {{- if .Arm }}v{{ .Arm }}{{ end }}
31 | # use zip for windows archives
32 | format_overrides:
33 | - goos: windows
34 | formats: [zip]
35 |
36 | changelog:
37 | sort: asc
38 | filters:
39 | exclude:
40 | - "^docs:"
41 | - "^test:"
42 | - "^chore:"
43 | - "^ci:"
44 |
45 | binary_signs:
46 | - cmd: cosign
47 | # copied from archives.name_template above
48 | signature: >-
49 | {{ .ProjectName }}_
50 | {{- title .Os }}_
51 | {{- if eq .Arch "amd64" }}x86_64
52 | {{- else if eq .Arch "386" }}i386
53 | {{- else }}{{ .Arch }}{{ end }}
54 | {{- if .Arm }}v{{ .Arm }}{{ end }}.cosign.bundle'
55 | args:
56 | - "sign-blob"
57 | - "--bundle=${signature}"
58 | - "${artifact}"
59 | - "--yes" # needed on cosign 2.0.0+
60 |
61 | brews:
62 | -
63 | repository:
64 | owner: mark-adams
65 | name: homebrew-gcp-ip-list
66 | branch: main
67 |
68 | git:
69 | url: 'ssh://git@github.com/mark-adams/homebrew-gcp-ip-list.git'
70 | private_key: '/tmp/HOMEBREW_GH_DEPLOY_KEY'
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributions are welcome! Before submitting a PR, please [file an issue](https://github.com/mark-adams/gcp-ip-list/issues) if one does not already exist to discuss your ideas! This can help avoid wasted effort and speed up the review process quite a bit!
2 |
3 | Once you've got your change ready, you can [submit a pull request](https://github.com/mark-adams/gcp-ip-list/compare). Please make sure your change has all of the following:
4 | - A good description of the change
5 | - Includes tests covering the new feature or fixed logic
6 |
7 | This project tries to follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) so please make sure your commit messages contain the proper prefixes. Some common examples are listed below.
8 |
9 | - `feat:` for a new feature or new functionality
10 | - `fix:` for bugfixes
11 | - `docs:` for changes to the documentation
12 | - `ci:` for changes to the build process
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
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:
--------------------------------------------------------------------------------
1 | # GCP IP List
2 |
3 | [](https://godoc.org/github.com/mark-adams/gcp-ip-list) [](https://raw.githubusercontent.com/mark-adams/gcp-ip-list/main/LICENSE) [](https://github.com/mark-adams/gcp-ip-list/actions/workflows/test.yml)
4 |
5 |
6 | `gcp-ip-list` is a 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. In addition, it is quite slow especially if you have a large number of projects.
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 | # Installation
13 |
14 |
15 |
16 | Homebrew (macOS or Linux) |
17 |
18 | brew tap mark-adams/gcp-ip-list && brew install gcp-ip-list
19 | |
20 |
21 |
22 |
23 | Pre-built binaries are also avalable from the [Releases page](https://github.com/mark-adams/gcp-ip-list/releases)
24 |
25 | If your system has a [supported version of Go](https://go.dev/dl/), you can build from source.
26 |
27 | ```
28 | go install github.com/mark-adams/gcp-ip-list/cmd/gcp-ip-list@latest
29 | ```
30 |
31 | # Running the tool
32 |
33 | This application authenticates with GCP using [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials). Some examples of how you might authneticate include:
34 |
35 | - Run the application on a GCP resource (VM, Cloud Function, etc.) with an attached service account
36 | - Run the application on your workstation after using `gcloud auth application-default login` to use your user account's credentials
37 | - Run the application on your workstation using a service account's credentials by running `gcloud auth activate-service-account`
38 |
39 | For more information on authenticating, see the [Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) documentation.
40 |
41 | Since this application uses the Cloud Asset Inventory APIs, your user account / service account will need to have the Cloud Asset Viewer (`roles/cloudasset.viewer`) IAM role assigned for the targeted scope's (i.e. organization, folder, or project) IAM policy.
42 |
43 | ## Usage
44 | ```
45 | $ gcp-ip-list -h
46 | Usage of gcp-ip-list:
47 | -format string
48 | The output format (csv, json, table, list) (default "table")
49 | -private
50 | Include private IPs only
51 | -public
52 | Include public IPs only
53 | -scope string
54 | The scope (organization, folder, or project) to search (i.e. projects/abc-123 or organizations/123456)
55 | -version
56 | Display the current version
57 | ```
58 |
59 | ### Use as a library
60 | Core functionality of the CLI is exposed via Go APIs as well in the `github.com/mark-adams/gcp-ip-list/pkg/go` package via the `GetAllAddressesFromAssetInventory()` and `GetAddressesFromAssetInventory()` functions in case you want to incoporate this functionality into your own application.
61 |
62 | ## Examples
63 |
64 | ### Table output
65 | ```
66 | $ gcp-ip-list --scope=projects/sample-project -public
67 | +----------------+--------------+---------------------------------------+-------------------------------------------------------------------------------------------------------------------------+
68 | | ADDRESS | ADDRESS TYPE | RESOURCE TYPE | RESOURCE NAME |
69 | +----------------+--------------+---------------------------------------+-------------------------------------------------------------------------------------------------------------------------+
70 | | 35.244.150.176 | public | compute.googleapis.com/ForwardingRule | //compute.googleapis.com/projects/sample-project/global/forwardingRules/ip-list-test-forwarding-rule-external |
71 | | 34.54.75.78 | public | compute.googleapis.com/ForwardingRule | //compute.googleapis.com/projects/sample-project/global/forwardingRules/ip-list-test-forwarding-rule-external-static |
72 | | 34.83.163.216 | public | compute.googleapis.com/Instance | //compute.googleapis.com/projects/sample-project/zones/us-west1-a/instances/ip-list-test-vm |
73 | | 34.105.8.244 | public | compute.googleapis.com/Router | //compute.googleapis.com/projects/sample-project/regions/us-west1/routers/ip-list-test-router |
74 | | 34.19.43.198 | public | container.googleapis.com/Cluster | //container.googleapis.com/projects/sample-project/locations/us-west1/clusters/ip-list-test-cluster |
75 | | 34.127.47.18 | public | sqladmin.googleapis.com/Instance | //cloudsql.googleapis.com/projects/sample-project/instances/ip-list-test-db |
76 | +----------------+--------------+---------------------------------------+-------------------------------------------------------------------------------------------------------------------------+
77 | ```
78 |
79 | ### List output
80 |
81 | ```
82 | $ gcp-ip-list --scope=projects/sample-project -public -format=list
83 | 35.244.150.176
84 | 34.54.75.78
85 | 34.83.163.216
86 | 34.105.8.244
87 | 34.19.43.198
88 | 34.127.47.18
89 | ```
90 |
91 | This mode is handy for piping to your favorite port scanning tool like `nmap` or `naabu`:
92 | ```
93 | gcp-ip-list --scope=projects/sample-project -public -format=list | nmap -iL -
94 | ```
95 |
96 | ### CSV & JSON output
97 |
98 | You can get the same output as the default table format but in CSV or JSON as well:
99 |
100 | ```
101 | gcp-ip-list --scope=projects/sample-project -public -format=csv
102 | ```
103 |
104 | ```
105 | gcp-ip-list --scope=projects/sample-project -public -format=json
106 | ```
107 |
108 | # Contributing
109 | See our [Contribution guidelines](CONTRIBUTING.md)
110 |
111 | ## Terraform resources
112 | The `terraform` directory contains sample resources that are handy when doing local development on `gcp-ip-list`.
113 | If you add support for a new resource type, please add the appropriate Terraform resources in the same PR.
114 |
115 | # Releases
116 | New releases can be found on the [Releases](page).
117 |
118 | ## Verifying signatures
119 | Binaries built by this project are signed using Sigstore.
120 |
121 | To verify the signature for a given binary, you can use [cosign](https://github.com/sigstore/cosign):
122 |
123 | ```
124 | $ cosign verify-blob gcp-ip-list_Darwin_x86_64/gcp-ip-list \
125 | --bundle gcp-ip-list_Darwin_x86_64.cosign.bundle \
126 | --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
127 | --certificate-identity=https://github.com/mark-adams/gcp-ip-list/.github/workflows/release.yml@refs/tags/
128 | Verified OK
129 | ```
130 |
131 | # Troubleshooting
132 |
133 | ## Could not find default credentials
134 |
135 | > error getting public addresses: error setting up client: credentials: could not find default credentials. See https://cloud.google.com/docs/authentication/external/set-up-adc for more information
136 |
137 | This means that you're likely running the tool locally from your workstation without having application default credentials set up. You can follow the link in the message or run `gcloud auth application-default login` to authenticate with GCP and obtain the proper credentials.
138 |
139 | ## Cloud Asset API has not been used in project X before
140 |
141 | This tool depends on the Cloud Asset Inventory API being enabled. Luckily, the error message points you in the right direction. Look for "Enable it by visiting https://..." in the error message and visit that page to enable the API.
142 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you identify a security vulnerability in this project, please report it responsibly by following the instructions below. Do NOT create a public issue for security issues.
6 |
7 | 1. Go to the repository's [Security Advisories](../../security/advisories) page.
8 | 2. Click on "Report a vulnerability."
9 | 3. Provide as much detail as possible about the issue, including steps to reproduce it if applicable.
10 |
11 | Once you have submitted your vulnerability report, it will be reviewed by the maintainers and resolved appropriately.
--------------------------------------------------------------------------------
/cmd/gcp-ip-list/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "cmp"
5 | "context"
6 | "flag"
7 | "fmt"
8 | "log"
9 | "os"
10 | "regexp"
11 | "slices"
12 |
13 | "github.com/mark-adams/gcp-ip-list/pkg/gcp"
14 | "github.com/mark-adams/gcp-ip-list/pkg/output"
15 | )
16 |
17 | var (
18 | version = "dev"
19 |
20 | scope = flag.String("scope", "", "The scope (organization, folder, or project) to search (i.e. projects/abc-123 or organizations/123456)")
21 | scopePattern = regexp.MustCompile(`^organizations/\d+$|^folders/\d+$|^projects/\S+$`)
22 |
23 | format = flag.String("format", "table", "The output format (csv, json, table, list)")
24 |
25 | public = flag.Bool("public", false, "Include public IPs only")
26 | private = flag.Bool("private", false, "Include private IPs only")
27 |
28 | showVersion = flag.Bool("version", false, "Display the current version")
29 | )
30 |
31 | func main() {
32 | log.SetFlags(0)
33 | flag.Parse()
34 |
35 | if *showVersion {
36 | fmt.Printf("gcp-ip-list %s\n", version)
37 | os.Exit(0)
38 | }
39 |
40 | if *scope == "" {
41 | log.Printf("error: scope flag is required (organizations/1234, folders/1234, or projects/1234)")
42 | os.Exit(1)
43 | }
44 | if !scopePattern.MatchString(*scope) {
45 | log.Fatalf("error: invalid scope: %s, scope must be organizations/1234, folders/1234, projects/1234", *scope)
46 | }
47 |
48 | if *public && *private {
49 | log.Fatalf("error: cannot specify both public and private flags")
50 | }
51 |
52 | formatters := output.GetFormatters()
53 |
54 | formatter := formatters[*format]
55 | if formatter == nil {
56 | log.Fatalf("error: invalid formatter: %s", *format)
57 | }
58 |
59 | ctx := context.Background()
60 |
61 | addresses, err := gcp.GetAllAddressesFromAssetInventory(ctx, *scope)
62 | if err != nil {
63 | log.Fatalf("error: failed to get addresses: %s", err)
64 | }
65 |
66 | if *public {
67 | addresses = gcp.FilterPublicAddresses(addresses)
68 | } else if *private {
69 | addresses = gcp.FilterPrivateAddresses(addresses)
70 | }
71 |
72 | // Sort the output by the address type (descending), then by resource type, then by resource name
73 | // (chosen somewhat arbitrarily)
74 | slices.SortFunc(addresses, func(a, b *gcp.Address) int {
75 | return cmp.Or(
76 | cmp.Compare(a.AddressType, b.AddressType)*-1,
77 | cmp.Compare(a.ResourceType, b.ResourceType),
78 | cmp.Compare(a.ResourceName, b.ResourceName),
79 | )
80 | })
81 |
82 | if err := formatter(os.Stdout, addresses); err != nil {
83 | log.Fatalf("error writing output: %s", err)
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mark-adams/gcp-ip-list
2 |
3 | go 1.24.1
4 |
5 | require (
6 | cloud.google.com/go/asset v1.20.5
7 | github.com/olekukonko/tablewriter v0.0.5
8 | github.com/stretchr/testify v1.10.0
9 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
10 | google.golang.org/api v0.228.0
11 | google.golang.org/grpc v1.71.0
12 | google.golang.org/protobuf v1.36.6
13 | )
14 |
15 | require (
16 | cloud.google.com/go v0.120.0 // indirect
17 | cloud.google.com/go/accesscontextmanager v1.9.5 // indirect
18 | cloud.google.com/go/auth v0.15.0 // indirect
19 | cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
20 | cloud.google.com/go/compute/metadata v0.6.0 // indirect
21 | cloud.google.com/go/iam v1.4.2 // indirect
22 | cloud.google.com/go/longrunning v0.6.6 // indirect
23 | cloud.google.com/go/orgpolicy v1.14.3 // indirect
24 | cloud.google.com/go/osconfig v1.14.4 // indirect
25 | github.com/davecgh/go-spew v1.1.1 // indirect
26 | github.com/felixge/httpsnoop v1.0.4 // indirect
27 | github.com/go-logr/logr v1.4.2 // indirect
28 | github.com/go-logr/stdr v1.2.2 // indirect
29 | github.com/google/s2a-go v0.1.9 // indirect
30 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
31 | github.com/googleapis/gax-go/v2 v2.14.1 // indirect
32 | github.com/mattn/go-runewidth v0.0.16 // indirect
33 | github.com/pmezard/go-difflib v1.0.0 // indirect
34 | github.com/rivo/uniseg v0.4.7 // indirect
35 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect
36 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
37 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
38 | go.opentelemetry.io/otel v1.35.0 // indirect
39 | go.opentelemetry.io/otel/metric v1.35.0 // indirect
40 | go.opentelemetry.io/otel/trace v1.35.0 // indirect
41 | golang.org/x/crypto v0.36.0 // indirect
42 | golang.org/x/net v0.38.0 // indirect
43 | golang.org/x/oauth2 v0.28.0 // indirect
44 | golang.org/x/sync v0.12.0 // indirect
45 | golang.org/x/sys v0.31.0 // indirect
46 | golang.org/x/text v0.23.0 // indirect
47 | golang.org/x/time v0.11.0 // indirect
48 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 // indirect
49 | google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect
50 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
51 | gopkg.in/yaml.v3 v3.0.1 // indirect
52 | )
53 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
2 | cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
3 | cloud.google.com/go/accesscontextmanager v1.9.5 h1:qfj57PJ0GSnrmKYNXnTQ8tuxjNQRW5ltLrlH/WNEypY=
4 | cloud.google.com/go/accesscontextmanager v1.9.5/go.mod h1:i6WSokkuePCT3jWwRzhge/pZicoErUBbDWjAUd8AoQU=
5 | cloud.google.com/go/asset v1.20.5 h1:VnGsFN1Ncj+UJfhIeVOIdxaIshccIc21r7B/VLPB7NM=
6 | cloud.google.com/go/asset v1.20.5/go.mod h1:0pbY+F3Pr3teQLK1ZXpUjGPNBPfUiL1tpxRxRmLCV/c=
7 | cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
8 | cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
9 | cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
10 | cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
11 | cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
12 | cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
13 | cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q=
14 | cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34=
15 | cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw=
16 | cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw=
17 | cloud.google.com/go/orgpolicy v1.14.3 h1:AvrSitEg4j/UHwxDfGekwUlRaGq0H2k63BiysNwTBrA=
18 | cloud.google.com/go/orgpolicy v1.14.3/go.mod h1:bc5nFdnE+4vwCLvv3uNFWUtsywFf6Szv+eW8SmAbQlQ=
19 | cloud.google.com/go/osconfig v1.14.4 h1:0UDagEY2Zo+cXv8OSCBM0E3APD2ziIupzcaWDLCJoe4=
20 | cloud.google.com/go/osconfig v1.14.4/go.mod h1:WQ5UV8yf1yhqrFrMD//dsqF/dqpepo9nzSF34aQ4vC8=
21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
24 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
25 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
26 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
27 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
28 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
29 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
30 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
31 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
32 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
33 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
34 | github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
35 | github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
36 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
37 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
38 | github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
39 | github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
40 | github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
41 | github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
42 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
43 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
44 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
45 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
46 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
47 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
48 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
49 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
50 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
51 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
52 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
53 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
54 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
55 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
56 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
57 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
58 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
59 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
60 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
61 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
62 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
63 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
64 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
65 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
66 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
67 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
68 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
69 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
70 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
71 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
72 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
73 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
74 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
75 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
76 | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
77 | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
78 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
79 | golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
80 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
81 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
82 | golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
83 | golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
84 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
85 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
86 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
87 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
88 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
89 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
90 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
91 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
92 | google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs=
93 | google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4=
94 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 h1:qEFnJI6AnfZk0NNe8YTyXQh5i//Zxi4gBHwRgp76qpw=
95 | google.golang.org/genproto v0.0.0-20250324211829-b45e905df463/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0=
96 | google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM=
97 | google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8=
98 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
99 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
100 | google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
101 | google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
102 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
103 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
104 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
105 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
106 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
107 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
108 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
109 |
--------------------------------------------------------------------------------
/pkg/gcp/api.go:
--------------------------------------------------------------------------------
1 | package gcp
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net"
7 | "slices"
8 |
9 | asset "cloud.google.com/go/asset/apiv1"
10 | assetpb "cloud.google.com/go/asset/apiv1/assetpb"
11 | "golang.org/x/exp/maps"
12 | "google.golang.org/api/iterator"
13 | "google.golang.org/api/option"
14 | "google.golang.org/protobuf/types/known/fieldmaskpb"
15 | )
16 |
17 | const (
18 | // AddressTypePublic refers to a public, internet-routable IP address
19 | AddressTypePublic = "public"
20 |
21 | // AddressTypePrivate refers to a private, non-internet-routable IP address
22 | AddressTypePrivate = "private"
23 |
24 | // Special placeholder to handle references to Address assets from NAT routers
25 | // This is needed because some assets only reference the Address resource name
26 | // that they are attached to instead of the IP address itself.
27 | // We use this placeholder to flag these assets so we can normalize them to the actual IP address
28 | // later. The output of the application should never show these.
29 | AddressTypeReference = "reference"
30 | )
31 |
32 | type Address struct {
33 | Address string `json:"address"`
34 | AddressType string `json:"type"`
35 | ResourceName string `json:"resource_name"`
36 | ResourceType string `json:"asset_type"`
37 | }
38 |
39 | // GetAllAddressesFromAssetInventory queries the Cloud Asset Inventory API and returns back IP addresses from all supported asset types
40 | func GetAllAddressesFromAssetInventory(ctx context.Context, scope string, opts ...option.ClientOption) ([]*Address, error) {
41 | return GetAddressesFromAssetInventory(ctx, scope, maps.Keys(getAddressByAssetType), opts...)
42 | }
43 |
44 | // GetAddressesFromAssetInventory queries the Cloud Asset Inventory API and returns back IP addresses from the specified asset types
45 | func GetAddressesFromAssetInventory(ctx context.Context, scope string, assetTypes []string, opts ...option.ClientOption) ([]*Address, error) {
46 | c, err := asset.NewClient(ctx, opts...)
47 | if err != nil {
48 | return nil, fmt.Errorf("error setting up client: %w", err)
49 | }
50 | defer c.Close() //nolint:errcheck
51 |
52 | for _, val := range assetTypes {
53 | if _, ok := getAddressByAssetType[val]; !ok {
54 | return nil, fmt.Errorf("unsupported asset type: %s", val)
55 | }
56 | }
57 |
58 | removeAddressesLater := false
59 |
60 | if slices.Contains(assetTypes, AssetTypeComputeRouter) && !slices.Contains(assetTypes, AssetTypeComputeAddress) {
61 | // NAT routers only reference the Address resource name, not the IP address itself.
62 | // As a result, if we are pulling down the NAT routers without pulling down the Address resources explicitly, we need to add it under the hood
63 | // so we can resolve the reference later
64 | assetTypes = append(assetTypes, AssetTypeComputeAddress)
65 | removeAddressesLater = true
66 | }
67 |
68 | req := &assetpb.SearchAllResourcesRequest{
69 | Scope: scope,
70 | AssetTypes: assetTypes,
71 | ReadMask: &fieldmaskpb.FieldMask{
72 | Paths: []string{"*"},
73 | },
74 | PageSize: 500,
75 | }
76 |
77 | it := c.SearchAllResources(ctx, req)
78 |
79 | var results []*Address
80 |
81 | for {
82 | resource, err := it.Next()
83 | if err == iterator.Done {
84 | break
85 | }
86 | if err != nil {
87 | return nil, fmt.Errorf("error searching for resources: %w", err)
88 | }
89 |
90 | addressGetter := getAddressByAssetType[resource.AssetType]
91 | if addressGetter == nil {
92 | return nil, fmt.Errorf("unexpected asset type: %s", resource.AssetType)
93 | }
94 |
95 | addresses := addressGetter(resource)
96 | results = append(results, addresses...)
97 | }
98 |
99 | return cleanupAssets(results, removeAddressesLater), nil
100 | }
101 |
102 | // cleanupAssets removes duplicate addresses from the list of assets
103 | func cleanupAssets(assets []*Address, removeAddresses bool) []*Address {
104 | // Resolve references to the IP of their associated Address resource
105 | computeAddressMap := map[string]*Address{}
106 | routerReferences := []*Address{}
107 |
108 | for _, addr := range assets {
109 | if addr.ResourceType == AssetTypeComputeAddress {
110 | computeAddressMap[addr.ResourceName] = addr
111 | }
112 |
113 | if addr.AddressType == "reference" && addr.ResourceType == AssetTypeComputeRouter {
114 | routerReferences = append(routerReferences, addr)
115 | }
116 | }
117 |
118 | for _, router := range routerReferences {
119 | if addr, ok := computeAddressMap[router.Address]; ok {
120 | router.Address = addr.Address
121 | router.AddressType = addr.AddressType
122 | }
123 | }
124 |
125 | // Remove reference assets from the list of assets since they should have been resolved above
126 | assets = slices.DeleteFunc(assets, func(a *Address) bool {
127 | return a.AddressType == "reference"
128 | })
129 |
130 | // Iterate over the list of assets and remove duplicate Address entries where the IP matches another
131 | // more-specific asset.
132 | seen := map[string]*Address{}
133 |
134 | for _, asset := range assets {
135 | if match, ok := seen[asset.Address]; !ok {
136 | seen[asset.Address] = asset
137 | } else {
138 | if match.ResourceType == AssetTypeComputeAddress {
139 | seen[asset.Address] = asset
140 | }
141 | }
142 | }
143 |
144 | addresses := []*Address{}
145 | for _, asset := range seen {
146 | if removeAddresses && asset.ResourceType == AssetTypeComputeAddress {
147 | continue
148 | }
149 | addresses = append(addresses, asset)
150 | }
151 |
152 | return addresses
153 | }
154 |
155 | func ipType(ip string) string {
156 | ipAddr := net.ParseIP(ip)
157 | if ipAddr.IsPrivate() {
158 | return AddressTypePrivate
159 | } else {
160 | return AddressTypePublic
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/pkg/gcp/filters.go:
--------------------------------------------------------------------------------
1 | package gcp
2 |
3 | // FilterPublicAddressees filters the given slice of addresses to only include public addresses
4 | func FilterPublicAddresses(addrs []*Address) []*Address {
5 | filtered := []*Address{}
6 |
7 | for _, a := range addrs {
8 | if a.AddressType != AddressTypePublic {
9 | continue
10 | }
11 | filtered = append(filtered, a)
12 | }
13 |
14 | return filtered
15 | }
16 |
17 | // FilterPrivateAddressees filters the given slice of addresses to only include private addresses
18 | func FilterPrivateAddresses(addrs []*Address) []*Address {
19 | filtered := []*Address{}
20 |
21 | for _, a := range addrs {
22 | if a.AddressType != AddressTypePrivate {
23 | continue
24 | }
25 | filtered = append(filtered, a)
26 | }
27 |
28 | return filtered
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/gcp/filters_test.go:
--------------------------------------------------------------------------------
1 | package gcp_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mark-adams/gcp-ip-list/pkg/gcp"
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestFilterPublicAddresses(t *testing.T) {
11 | testAddresses := []*gcp.Address{
12 | {
13 | Address: "10.0.1.2",
14 | AddressType: gcp.AddressTypePrivate,
15 | },
16 | {
17 | Address: "92.12.13.14",
18 | AddressType: gcp.AddressTypePublic,
19 | },
20 | }
21 |
22 | filtered := gcp.FilterPublicAddresses(testAddresses)
23 |
24 | require.Len(t, filtered, 1)
25 | require.EqualValues(t, testAddresses[1:2], filtered)
26 | }
27 |
28 | func TestFilterPrivateAddresses(t *testing.T) {
29 | testAddresses := []*gcp.Address{
30 | {
31 | Address: "10.0.1.2",
32 | AddressType: gcp.AddressTypePrivate,
33 | },
34 | {
35 | Address: "92.12.13.14",
36 | AddressType: gcp.AddressTypePublic,
37 | },
38 | }
39 |
40 | filtered := gcp.FilterPrivateAddresses(testAddresses)
41 |
42 | require.Len(t, filtered, 1)
43 | require.EqualValues(t, testAddresses[0:1], filtered)
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/gcp/parsing.go:
--------------------------------------------------------------------------------
1 | package gcp
2 |
3 | import (
4 | "strings"
5 |
6 | assetpb "cloud.google.com/go/asset/apiv1/assetpb"
7 | )
8 |
9 | type AddressGetter func(resource *assetpb.ResourceSearchResult) []*Address
10 |
11 | const (
12 | AssetTypeComputeInstance = "compute.googleapis.com/Instance"
13 | AssetTypeComputeAddress = "compute.googleapis.com/Address"
14 | AssetTypeCloudSQLInstance = "sqladmin.googleapis.com/Instance"
15 | AssetTypeContainerCluster = "container.googleapis.com/Cluster"
16 | AssetTypeComputeForwardingRule = "compute.googleapis.com/ForwardingRule"
17 | AssetTypeComputeRouter = "compute.googleapis.com/Router"
18 | )
19 |
20 | var getAddressByAssetType = map[string]AddressGetter{
21 | AssetTypeComputeInstance: getAddressForGCEInstance,
22 | AssetTypeComputeAddress: getAddressForAddress,
23 | AssetTypeCloudSQLInstance: getAddressForSQLInstances,
24 | AssetTypeContainerCluster: getAddressForGKECluster,
25 | AssetTypeComputeForwardingRule: getAddressForForwardingRule,
26 | AssetTypeComputeRouter: getAddressForRouter,
27 | }
28 |
29 | func getAddressForGCEInstance(resource *assetpb.ResourceSearchResult) []*Address {
30 | ipStrings := []string{}
31 |
32 | externalIPs := resource.AdditionalAttributes.Fields["externalIPs"]
33 | if externalIPs != nil {
34 | ipList := externalIPs.GetListValue()
35 | if ipList == nil || len(ipList.Values) == 0 {
36 | return nil
37 | } else {
38 | for _, ip := range ipList.Values {
39 | ipVal := ip.GetStringValue()
40 | if ip.GetStringValue() != "" {
41 | ipStrings = append(ipStrings, ipVal)
42 | }
43 | }
44 | }
45 | }
46 |
47 | internalIPs := resource.AdditionalAttributes.Fields["internalIPs"]
48 | if internalIPs != nil {
49 | ipList := internalIPs.GetListValue()
50 | if ipList == nil || len(ipList.Values) == 0 {
51 | return nil
52 | } else {
53 | for _, ip := range ipList.Values {
54 | ipVal := ip.GetStringValue()
55 | if ip.GetStringValue() != "" {
56 | ipStrings = append(ipStrings, ipVal)
57 | }
58 | }
59 | }
60 | }
61 |
62 | addresses := []*Address{}
63 | for _, ip := range ipStrings {
64 | addresses = append(addresses, &Address{
65 | Address: ip,
66 | ResourceName: resource.Name,
67 | AddressType: ipType(ip),
68 | ResourceType: resource.AssetType,
69 | })
70 | }
71 |
72 | return addresses
73 | }
74 |
75 | func getAddressForAddress(resource *assetpb.ResourceSearchResult) []*Address {
76 | if resource.State != "IN_USE" {
77 | return nil
78 | }
79 |
80 | address := resource.AdditionalAttributes.Fields["address"]
81 | if address == nil {
82 | return nil
83 | }
84 |
85 | addressStr := address.GetStringValue()
86 | if addressStr == "" {
87 | return nil
88 | }
89 |
90 | return []*Address{
91 | {
92 | Address: addressStr,
93 | ResourceName: resource.Name,
94 | AddressType: ipType(addressStr),
95 | ResourceType: resource.AssetType,
96 | },
97 | }
98 | }
99 |
100 | func getAddressForSQLInstances(resource *assetpb.ResourceSearchResult) []*Address {
101 | dbResources := resource.GetVersionedResources()
102 | if len(dbResources) == 0 {
103 | return nil
104 | }
105 |
106 | dbResource := dbResources[0]
107 | if dbResource == nil {
108 | return nil
109 | }
110 |
111 | dbResourceValues := dbResource.GetResource()
112 | if dbResourceValues == nil {
113 | return nil
114 | }
115 |
116 | addresses := dbResourceValues.GetFields()["ipAddresses"]
117 | addressesList := addresses.GetListValue()
118 | addressesListValues := addressesList.GetValues()
119 |
120 | publicAddresses := []*Address{}
121 |
122 | for _, address := range addressesListValues {
123 | addressFields := address.GetStructValue().GetFields()
124 | typeValue := addressFields["type"].GetStringValue()
125 |
126 | if typeValue == "OUTGOING" {
127 | continue
128 | }
129 |
130 | addressValue := addressFields["ipAddress"].GetStringValue()
131 | publicAddresses = append(publicAddresses, &Address{
132 | Address: addressValue,
133 | ResourceName: resource.Name,
134 | AddressType: ipType(addressValue),
135 | ResourceType: resource.AssetType,
136 | })
137 |
138 | }
139 |
140 | return publicAddresses
141 | }
142 |
143 | func getAddressForGKECluster(resource *assetpb.ResourceSearchResult) []*Address {
144 | clusterResources := resource.GetVersionedResources()
145 | if len(clusterResources) == 0 {
146 | return nil
147 | }
148 |
149 | clusterResource := clusterResources[0]
150 | if clusterResource == nil {
151 | return nil
152 | }
153 |
154 | clusterResourceValues := clusterResource.GetResource()
155 | if clusterResourceValues == nil {
156 | return nil
157 | }
158 |
159 | privateClusterConfig := clusterResourceValues.GetFields()["privateClusterConfig"].GetStructValue()
160 | ipStrings := []string{}
161 |
162 | publicEndpoint := privateClusterConfig.GetFields()["publicEndpoint"]
163 |
164 | if publicEndpoint != nil && publicEndpoint.GetStringValue() != "" {
165 | ipStrings = append(ipStrings, publicEndpoint.GetStringValue())
166 | }
167 |
168 | privateEndpoint := privateClusterConfig.GetFields()["privateEndpoint"]
169 |
170 | if privateEndpoint != nil && privateEndpoint.GetStringValue() != "" {
171 | ipStrings = append(ipStrings, privateEndpoint.GetStringValue())
172 | }
173 |
174 | addresses := []*Address{}
175 |
176 | for _, ip := range ipStrings {
177 | addresses = append(addresses, &Address{
178 | Address: ip,
179 | ResourceName: resource.Name,
180 | AddressType: ipType(ip),
181 | ResourceType: resource.AssetType,
182 | })
183 | }
184 |
185 | return addresses
186 | }
187 |
188 | func getAddressForForwardingRule(resource *assetpb.ResourceSearchResult) []*Address {
189 | ruleResources := resource.GetVersionedResources()
190 | if len(ruleResources) == 0 {
191 | return nil
192 | }
193 |
194 | ruleResource := ruleResources[0]
195 | if ruleResource == nil {
196 | return nil
197 | }
198 |
199 | ruleResourceValues := ruleResource.GetResource()
200 | if ruleResourceValues == nil {
201 | return nil
202 | }
203 |
204 | addressField := ruleResourceValues.GetFields()["IPAddress"]
205 |
206 | if addressField == nil || addressField.GetStringValue() == "" {
207 | return nil
208 | }
209 |
210 | address := addressField.GetStringValue()
211 |
212 | return []*Address{
213 | {
214 | Address: address,
215 | ResourceName: resource.Name,
216 | AddressType: ipType(address),
217 | ResourceType: resource.AssetType,
218 | },
219 | }
220 | }
221 |
222 | func getAddressForRouter(resource *assetpb.ResourceSearchResult) []*Address {
223 | routerResources := resource.GetVersionedResources()
224 | if len(routerResources) == 0 {
225 | return nil
226 | }
227 |
228 | routerResource := routerResources[0]
229 | if routerResource == nil {
230 | return nil
231 | }
232 |
233 | routerResourceValues := routerResource.GetResource()
234 | if routerResourceValues == nil {
235 | return nil
236 | }
237 |
238 | natsField := routerResourceValues.GetFields()["nats"]
239 |
240 | if natsField == nil || natsField.GetListValue() == nil {
241 | return nil
242 | }
243 |
244 | natsList := natsField.GetListValue()
245 | natsListValues := natsList.GetValues()
246 |
247 | addresses := []*Address{}
248 |
249 | for _, nat := range natsListValues {
250 | natFields := nat.GetStructValue().GetFields()
251 | natIpsField := natFields["natIps"]
252 | if natIpsField == nil || natIpsField.GetListValue() == nil {
253 | continue
254 | }
255 |
256 | natIpsFieldList := natIpsField.GetListValue()
257 | for _, ip := range natIpsFieldList.GetValues() {
258 | ref := ip.GetStringValue()
259 | addresses = append(addresses, &Address{
260 | Address: strings.Replace(ref, "https://www.googleapis.com/compute/v1/", "//compute.googleapis.com/", 1),
261 | ResourceName: resource.Name,
262 | AddressType: AddressTypeReference,
263 | ResourceType: resource.AssetType,
264 | })
265 | }
266 | }
267 |
268 | return addresses
269 | }
270 |
--------------------------------------------------------------------------------
/pkg/gcp/parsing_test.go:
--------------------------------------------------------------------------------
1 | package gcp_test
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "net"
10 | "os"
11 | "slices"
12 | "testing"
13 |
14 | assetpb "cloud.google.com/go/asset/apiv1/assetpb"
15 | "github.com/mark-adams/gcp-ip-list/pkg/gcp"
16 | "github.com/stretchr/testify/require"
17 | "google.golang.org/api/option"
18 | "google.golang.org/grpc"
19 | "google.golang.org/grpc/credentials/insecure"
20 | "google.golang.org/protobuf/encoding/protojson"
21 | )
22 |
23 | const scope = "projects/fuzzy-pickles-428115"
24 |
25 | type fakeAssetInventoryServer struct {
26 | assets []*assetpb.ResourceSearchResult
27 |
28 | assetpb.UnimplementedAssetServiceServer
29 | }
30 |
31 | func (f *fakeAssetInventoryServer) SearchAllResources(ctx context.Context, req *assetpb.SearchAllResourcesRequest) (*assetpb.SearchAllResourcesResponse, error) {
32 | response := assetpb.SearchAllResourcesResponse{}
33 |
34 | assetTypes := req.AssetTypes
35 | assets := []*assetpb.ResourceSearchResult{}
36 |
37 | for _, asset := range f.assets {
38 | if slices.Contains(assetTypes, asset.AssetType) {
39 | assets = append(assets, asset)
40 | }
41 | }
42 |
43 | response.Results = assets
44 | return &response, nil
45 | }
46 |
47 | func setupTestServer() (net.Listener, error) {
48 | f, err := os.Open("test-data/assets.json")
49 | if err != nil {
50 | return nil, fmt.Errorf("error opening asset test data file: %w", err)
51 | }
52 | defer f.Close() //nolint:errcheck
53 |
54 | assetBytes, err := io.ReadAll(f)
55 | if err != nil {
56 | return nil, fmt.Errorf("error reading asset test data file: %w", err)
57 | }
58 |
59 | assetObjs := []json.RawMessage{}
60 | if err := json.Unmarshal(assetBytes, &assetObjs); err != nil {
61 | return nil, fmt.Errorf("error parsing asset test data: %w", err)
62 | }
63 |
64 | assets := []*assetpb.ResourceSearchResult{}
65 |
66 | for _, assetObj := range assetObjs {
67 | asset := assetpb.ResourceSearchResult{}
68 | if err := protojson.Unmarshal(assetObj, &asset); err != nil {
69 | return nil, fmt.Errorf("error parsing search result json: %w", err)
70 | }
71 | assets = append(assets, &asset)
72 | }
73 |
74 | assetInventoryServer := fakeAssetInventoryServer{assets: assets}
75 | gsrv := grpc.NewServer()
76 | assetpb.RegisterAssetServiceServer(gsrv, &assetInventoryServer)
77 |
78 | listener, err := net.Listen("tcp", "localhost:0")
79 | if err != nil {
80 | return nil, fmt.Errorf("error setting up listener for test server: %w", err)
81 | }
82 |
83 | go func() {
84 | if err := gsrv.Serve(listener); err != nil {
85 | var nerr *net.OpError
86 |
87 | if errors.As(err, &nerr) {
88 | if nerr.Err.Error() == "use of closed network connection" {
89 | return
90 | }
91 | }
92 |
93 | panic(err)
94 | }
95 | }()
96 |
97 | return listener, nil
98 | }
99 |
100 | func TestGetAssets(t *testing.T) {
101 | testcases := []struct {
102 | name string
103 | assetTypes []string
104 | expectedAddresses []*gcp.Address
105 | }{
106 | {
107 | name: "compute instances",
108 | assetTypes: []string{"compute.googleapis.com/Instance"},
109 | expectedAddresses: []*gcp.Address{
110 | {
111 | Address: "34.83.128.26",
112 | AddressType: "public",
113 | ResourceType: "compute.googleapis.com/Instance",
114 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/zones/us-west1-a/instances/ip-list-test-vm",
115 | },
116 | {
117 | Address: "10.0.3.2",
118 | AddressType: "private",
119 | ResourceType: "compute.googleapis.com/Instance",
120 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/zones/us-west1-a/instances/ip-list-test-vm",
121 | },
122 | },
123 | },
124 | {
125 | name: "cloudsql_instances",
126 | assetTypes: []string{"sqladmin.googleapis.com/Instance"},
127 | expectedAddresses: []*gcp.Address{
128 | {
129 | Address: "35.247.31.30",
130 | AddressType: "public",
131 | ResourceName: "//cloudsql.googleapis.com/projects/fuzzy-pickles-428115/instances/ip-list-test-db",
132 | ResourceType: "sqladmin.googleapis.com/Instance",
133 | },
134 | {
135 | Address: "10.252.0.3",
136 | AddressType: "private",
137 | ResourceName: "//cloudsql.googleapis.com/projects/fuzzy-pickles-428115/instances/ip-list-test-db",
138 | ResourceType: "sqladmin.googleapis.com/Instance",
139 | },
140 | },
141 | },
142 | {
143 | name: "load_balancers",
144 | assetTypes: []string{"compute.googleapis.com/ForwardingRule"},
145 | expectedAddresses: []*gcp.Address{
146 | {
147 | Address: "34.54.244.120",
148 | AddressType: "public",
149 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external-static",
150 | ResourceType: "compute.googleapis.com/ForwardingRule",
151 | },
152 | {
153 | Address: "34.54.243.87",
154 | AddressType: "public",
155 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external",
156 | ResourceType: "compute.googleapis.com/ForwardingRule",
157 | },
158 | {
159 | Address: "10.0.2.2",
160 | AddressType: "private",
161 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-internal",
162 | ResourceType: "compute.googleapis.com/ForwardingRule",
163 | },
164 | },
165 | },
166 | {
167 | name: "gke_cluster",
168 | assetTypes: []string{"container.googleapis.com/Cluster"},
169 | expectedAddresses: []*gcp.Address{
170 | {
171 | Address: "34.105.114.31",
172 | AddressType: "public",
173 | ResourceName: "//container.googleapis.com/projects/fuzzy-pickles-428115/locations/us-west1/clusters/ip-list-test-cluster",
174 | ResourceType: "container.googleapis.com/Cluster",
175 | },
176 | {
177 | Address: "10.138.0.2",
178 | AddressType: "private",
179 | ResourceName: "//container.googleapis.com/projects/fuzzy-pickles-428115/locations/us-west1/clusters/ip-list-test-cluster",
180 | ResourceType: "container.googleapis.com/Cluster",
181 | },
182 | },
183 | },
184 | {
185 | name: "nat_router",
186 | assetTypes: []string{"compute.googleapis.com/Router"},
187 | expectedAddresses: []*gcp.Address{
188 | {
189 | Address: "34.19.80.22",
190 | AddressType: "public",
191 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/regions/us-west1/routers/ip-list-test-router",
192 | ResourceType: "compute.googleapis.com/Router",
193 | },
194 | },
195 | },
196 | {
197 | name: "addresses",
198 | assetTypes: []string{"compute.googleapis.com/Address"},
199 | expectedAddresses: []*gcp.Address{
200 | {
201 | Address: "34.19.80.22",
202 | AddressType: "public",
203 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/regions/us-west1/addresses/ip-list-test-nat",
204 | ResourceType: "compute.googleapis.com/Address",
205 | },
206 | {
207 | Address: "34.54.244.120",
208 | AddressType: "public",
209 | ResourceName: "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/addresses/ip-list-test-static-address",
210 | ResourceType: "compute.googleapis.com/Address",
211 | },
212 | },
213 | },
214 | }
215 |
216 | server, err := setupTestServer()
217 | if err != nil {
218 | t.Fatalf("error setting up test server: %s", err)
219 | }
220 | defer server.Close() //nolint:errcheck
221 |
222 | for _, tc := range testcases {
223 | t.Run(tc.name, func(t *testing.T) {
224 | addr, err := gcp.GetAddressesFromAssetInventory(
225 | context.Background(),
226 | scope,
227 | tc.assetTypes,
228 |
229 | // These are necessary to get the Google Cloud SDK to use the fake grpc server
230 | option.WithEndpoint(server.Addr().String()),
231 | option.WithoutAuthentication(),
232 | option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())),
233 | )
234 | if err != nil {
235 | t.Fatalf("error getting addresses from asset inventory: %s", err)
236 | }
237 |
238 | require.ElementsMatch(t, tc.expectedAddresses, addr)
239 | })
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/pkg/gcp/test-data/assets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "assetType": "sqladmin.googleapis.com/Instance",
4 | "createTime": "2024-07-01T16:29:10Z",
5 | "displayName": "ip-list-test-db",
6 | "location": "us-west1",
7 | "name": "//cloudsql.googleapis.com/projects/fuzzy-pickles-428115/instances/ip-list-test-db",
8 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
9 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
10 | "project": "projects/828107101350",
11 | "state": "RUNNABLE",
12 | "versionedResources": [
13 | {
14 | "resource": {
15 | "backendType": "SECOND_GEN",
16 | "connectionName": "fuzzy-pickles-428115:us-west1:ip-list-test-db",
17 | "createTime": "2024-07-01T16:29:10.763Z",
18 | "databaseInstalledVersion": "POSTGRES_15_7",
19 | "databaseVersion": "POSTGRES_15",
20 | "gceZone": "us-west1-c",
21 | "geminiConfig": {
22 | "activeQueryEnabled": false,
23 | "entitled": false,
24 | "googleVacuumMgmtEnabled": false,
25 | "indexAdvisorEnabled": false,
26 | "oomSessionCancelEnabled": false
27 | },
28 | "instanceType": "CLOUD_SQL_INSTANCE",
29 | "ipAddresses": [
30 | {
31 | "ipAddress": "35.247.31.30",
32 | "type": "PRIMARY"
33 | },
34 | {
35 | "ipAddress": "34.168.47.251",
36 | "type": "OUTGOING"
37 | },
38 | {
39 | "ipAddress": "10.252.0.3",
40 | "type": "PRIVATE"
41 | }
42 | ],
43 | "kind": "sql#instance",
44 | "maintenanceVersion": "POSTGRES_15_7.R20240514.00_06",
45 | "name": "ip-list-test-db",
46 | "project": "fuzzy-pickles-428115",
47 | "region": "us-west1",
48 | "selfLink": "https://sqladmin.googleapis.com/sql/v1beta4/projects/fuzzy-pickles-428115/instances/ip-list-test-db",
49 | "serverCaCert": {
50 | "certSerialNumber": "0",
51 | "commonName": "C=US,O=Google\\, Inc,CN=Google Cloud SQL Server CA,dnQualifier=cb3ffc5a-186b-4f7c-9776-ba7954dbe91d",
52 | "createTime": "2024-07-01T16:32:49.374Z",
53 | "expirationTime": "2034-06-29T16:33:49.374Z",
54 | "instance": "ip-list-test-db",
55 | "kind": "sql#sslCert",
56 | "sha1Fingerprint": "a51df67568de4c795df284ef2eca8df9699783ab"
57 | },
58 | "serviceAccountEmailAddress": "p828107101350-iz9ajm@gcp-sa-cloud-sql.iam.gserviceaccount.com",
59 | "settings": {
60 | "activationPolicy": "ALWAYS",
61 | "availabilityType": "ZONAL",
62 | "backupConfiguration": {
63 | "backupRetentionSettings": {
64 | "retainedBackups": 7,
65 | "retentionUnit": "COUNT"
66 | },
67 | "enabled": false,
68 | "kind": "sql#backupConfiguration",
69 | "startTime": "04:00",
70 | "transactionLogRetentionDays": 7,
71 | "transactionalLogStorageState": "TRANSACTIONAL_LOG_STORAGE_STATE_UNSPECIFIED"
72 | },
73 | "connectorEnforcement": "NOT_REQUIRED",
74 | "dataDiskSizeGb": "10",
75 | "dataDiskType": "PD_SSD",
76 | "deletionProtectionEnabled": false,
77 | "edition": "ENTERPRISE",
78 | "enableGoogleMlIntegration": false,
79 | "ipConfiguration": {
80 | "enablePrivatePathForGoogleCloudServices": false,
81 | "ipv4Enabled": true,
82 | "privateNetwork": "projects/fuzzy-pickles-428115/global/networks/public-ip-list-network",
83 | "requireSsl": false,
84 | "sslMode": "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
85 | },
86 | "kind": "sql#settings",
87 | "locationPreference": {
88 | "kind": "sql#locationPreference",
89 | "zone": "us-west1-c"
90 | },
91 | "pricingPlan": "PER_USE",
92 | "replicationType": "SYNCHRONOUS",
93 | "settingsVersion": "1",
94 | "storageAutoResize": true,
95 | "storageAutoResizeLimit": "0",
96 | "tier": "db-f1-micro"
97 | },
98 | "sqlNetworkArchitecture": "NEW_NETWORK_ARCHITECTURE",
99 | "state": "RUNNABLE",
100 | "upgradableDatabaseVersions": [
101 | {
102 | "displayName": "PostgreSQL 16",
103 | "majorVersion": "POSTGRES_16",
104 | "name": "POSTGRES_16"
105 | }
106 | ]
107 | },
108 | "version": "v1beta4"
109 | }
110 | ]
111 | },
112 | {
113 | "additionalAttributes": {
114 | "IPAddress": "34.54.244.120"
115 | },
116 | "assetType": "compute.googleapis.com/ForwardingRule",
117 | "createTime": "2024-07-01T16:17:45Z",
118 | "displayName": "ip-list-test-forwarding-rule-external-static",
119 | "location": "global",
120 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external-static",
121 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
122 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
123 | "project": "projects/828107101350",
124 | "state": "UNSPECIFIED",
125 | "versionedResources": [
126 | {
127 | "resource": {
128 | "IPAddress": "34.54.244.120",
129 | "IPProtocol": "TCP",
130 | "creationTimestamp": "2024-07-01T09:17:45.005-07:00",
131 | "description": "",
132 | "fingerprint": "30EfwXVW+SI=",
133 | "id": "5285103070662846535",
134 | "labelFingerprint": "42WmSpB8rSM=",
135 | "loadBalancingScheme": "EXTERNAL_MANAGED",
136 | "name": "ip-list-test-forwarding-rule-external-static",
137 | "networkTier": "PREMIUM",
138 | "portRange": "80-80",
139 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external-static",
140 | "target": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/targetHttpProxies/ip-list-test-http-proxy"
141 | },
142 | "version": "v1"
143 | }
144 | ]
145 | },
146 | {
147 | "additionalAttributes": {
148 | "IPAddress": "34.54.243.87"
149 | },
150 | "assetType": "compute.googleapis.com/ForwardingRule",
151 | "createTime": "2024-07-01T16:17:44Z",
152 | "displayName": "ip-list-test-forwarding-rule-external",
153 | "location": "global",
154 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external",
155 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
156 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
157 | "project": "projects/828107101350",
158 | "state": "UNSPECIFIED",
159 | "versionedResources": [
160 | {
161 | "resource": {
162 | "IPAddress": "34.54.243.87",
163 | "IPProtocol": "TCP",
164 | "creationTimestamp": "2024-07-01T09:17:44.848-07:00",
165 | "description": "",
166 | "fingerprint": "bgYy/WUxi/E=",
167 | "id": "9188148613718435911",
168 | "labelFingerprint": "42WmSpB8rSM=",
169 | "loadBalancingScheme": "EXTERNAL_MANAGED",
170 | "name": "ip-list-test-forwarding-rule-external",
171 | "networkTier": "PREMIUM",
172 | "portRange": "80-80",
173 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external",
174 | "target": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/targetHttpProxies/ip-list-test-http-proxy"
175 | },
176 | "version": "v1"
177 | }
178 | ]
179 | },
180 | {
181 | "additionalAttributes": {
182 | "IPAddress": "10.0.2.2"
183 | },
184 | "assetType": "compute.googleapis.com/ForwardingRule",
185 | "createTime": "2024-07-01T16:17:23Z",
186 | "displayName": "ip-list-test-forwarding-rule-internal",
187 | "location": "global",
188 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-internal",
189 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
190 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
191 | "project": "projects/828107101350",
192 | "state": "UNSPECIFIED",
193 | "versionedResources": [
194 | {
195 | "resource": {
196 | "IPAddress": "10.0.2.2",
197 | "IPProtocol": "TCP",
198 | "creationTimestamp": "2024-07-01T09:17:23.267-07:00",
199 | "description": "",
200 | "fingerprint": "u73W22hq6w8=",
201 | "id": "4239899161156085884",
202 | "labelFingerprint": "42WmSpB8rSM=",
203 | "loadBalancingScheme": "INTERNAL_MANAGED",
204 | "name": "ip-list-test-forwarding-rule-internal",
205 | "network": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/networks/public-ip-list-network",
206 | "networkTier": "PREMIUM",
207 | "portRange": "80-80",
208 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-internal",
209 | "subnetwork": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/subnetworks/lb-subnet",
210 | "target": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/targetHttpProxies/ip-list-test-http-proxy-internal"
211 | },
212 | "version": "v1"
213 | }
214 | ]
215 | },
216 | {
217 | "additionalAttributes": {
218 | "address": "34.19.68.170"
219 | },
220 | "assetType": "compute.googleapis.com/Address",
221 | "createTime": "2024-07-01T16:16:37Z",
222 | "displayName": "ip-list-test-public-not-used",
223 | "location": "us-west1",
224 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/regions/us-west1/addresses/ip-list-test-public-not-used",
225 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
226 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
227 | "project": "projects/828107101350",
228 | "state": "RESERVED",
229 | "versionedResources": [
230 | {
231 | "resource": {
232 | "address": "34.19.68.170",
233 | "addressType": "EXTERNAL",
234 | "creationTimestamp": "2024-07-01T09:16:37.153-07:00",
235 | "description": "",
236 | "id": "1537931816448518282",
237 | "labelFingerprint": "42WmSpB8rSM=",
238 | "name": "ip-list-test-public-not-used",
239 | "networkTier": "PREMIUM",
240 | "region": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1",
241 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/addresses/ip-list-test-public-not-used",
242 | "status": "RESERVED"
243 | },
244 | "version": "v1"
245 | }
246 | ]
247 | },
248 | {
249 | "additionalAttributes": {
250 | "address": "34.19.80.22"
251 | },
252 | "assetType": "compute.googleapis.com/Address",
253 | "createTime": "2024-07-01T16:16:36Z",
254 | "displayName": "ip-list-test-nat",
255 | "location": "us-west1",
256 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/regions/us-west1/addresses/ip-list-test-nat",
257 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
258 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
259 | "project": "projects/828107101350",
260 | "state": "IN_USE",
261 | "versionedResources": [
262 | {
263 | "resource": {
264 | "address": "34.19.80.22",
265 | "addressType": "EXTERNAL",
266 | "creationTimestamp": "2024-07-01T09:16:36.983-07:00",
267 | "description": "",
268 | "id": "8865645897488230539",
269 | "labelFingerprint": "42WmSpB8rSM=",
270 | "name": "ip-list-test-nat",
271 | "networkTier": "PREMIUM",
272 | "region": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1",
273 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/addresses/ip-list-test-nat",
274 | "status": "IN_USE",
275 | "users": [
276 | "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/routers/ip-list-test-router"
277 | ]
278 | },
279 | "version": "v1"
280 | }
281 | ]
282 | },
283 | {
284 | "additionalAttributes": {
285 | "deletionProtection": "FALSE",
286 | "externalIPs": [
287 | "34.83.128.26"
288 | ],
289 | "id": "3849170704745326773",
290 | "internalIPs": [
291 | "10.0.3.2"
292 | ],
293 | "machineType": "f1-micro",
294 | "networkInterfaceNames": [
295 | "nic0"
296 | ],
297 | "networkInterfaceNetworks": [
298 | "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/networks/public-ip-list-network"
299 | ],
300 | "networkInterfaceStackTypes": [
301 | "IPV4_ONLY"
302 | ]
303 | },
304 | "assetType": "compute.googleapis.com/Instance",
305 | "createTime": "2024-07-01T16:16:26Z",
306 | "displayName": "ip-list-test-vm",
307 | "location": "us-west1-a",
308 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/zones/us-west1-a/instances/ip-list-test-vm",
309 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
310 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
311 | "project": "projects/828107101350",
312 | "state": "RUNNING",
313 | "versionedResources": [
314 | {
315 | "resource": {
316 | "canIpForward": false,
317 | "cpuPlatform": "Intel Broadwell",
318 | "creationTimestamp": "2024-07-01T09:16:26.669-07:00",
319 | "deletionProtection": false,
320 | "disks": [
321 | {
322 | "architecture": "X86_64",
323 | "autoDelete": true,
324 | "boot": true,
325 | "deviceName": "persistent-disk-0",
326 | "diskSizeGb": "10",
327 | "guestOsFeatures": [
328 | {
329 | "type": "UEFI_COMPATIBLE"
330 | },
331 | {
332 | "type": "VIRTIO_SCSI_MULTIQUEUE"
333 | },
334 | {
335 | "type": "GVNIC"
336 | },
337 | {
338 | "type": "SEV_CAPABLE"
339 | },
340 | {
341 | "type": "SEV_LIVE_MIGRATABLE_V2"
342 | }
343 | ],
344 | "index": 0,
345 | "interface": "SCSI",
346 | "licenses": [
347 | "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/licenses/debian-12-bookworm"
348 | ],
349 | "mode": "READ_WRITE",
350 | "source": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/zones/us-west1-a/disks/ip-list-test-vm",
351 | "type": "PERSISTENT"
352 | }
353 | ],
354 | "fingerprint": "b127fMiJTS4=",
355 | "id": "3849170704745326773",
356 | "labelFingerprint": "42WmSpB8rSM=",
357 | "lastStartTimestamp": "2024-07-01T09:16:34.058-07:00",
358 | "machineType": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/zones/us-west1-a/machineTypes/f1-micro",
359 | "name": "ip-list-test-vm",
360 | "networkInterfaces": [
361 | {
362 | "accessConfigs": [
363 | {
364 | "name": "external-nat",
365 | "natIP": "34.83.128.26",
366 | "networkTier": "PREMIUM",
367 | "type": "ONE_TO_ONE_NAT"
368 | }
369 | ],
370 | "fingerprint": "YpNeZh6SOh0=",
371 | "name": "nic0",
372 | "network": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/networks/public-ip-list-network",
373 | "networkIP": "10.0.3.2",
374 | "stackType": "IPV4_ONLY",
375 | "subnetwork": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/subnetworks/backend-subnet"
376 | }
377 | ],
378 | "resourceStatus": {},
379 | "scheduling": {
380 | "automaticRestart": true,
381 | "onHostMaintenance": "MIGRATE",
382 | "preemptible": false,
383 | "provisioningModel": "STANDARD"
384 | },
385 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/zones/us-west1-a/instances/ip-list-test-vm",
386 | "shieldedInstanceConfig": {
387 | "enableIntegrityMonitoring": true,
388 | "enableSecureBoot": false,
389 | "enableVtpm": true
390 | },
391 | "shieldedInstanceIntegrityPolicy": {
392 | "updateAutoLearnPolicy": true
393 | },
394 | "startRestricted": false,
395 | "status": "RUNNING",
396 | "tags": {
397 | "fingerprint": "42WmSpB8rSM="
398 | },
399 | "zone": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/zones/us-west1-a"
400 | },
401 | "version": "v1"
402 | }
403 | ]
404 | },
405 | {
406 | "assetType": "compute.googleapis.com/Router",
407 | "createTime": "2024-07-01T16:16:25Z",
408 | "displayName": "ip-list-test-router",
409 | "location": "us-west1",
410 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/regions/us-west1/routers/ip-list-test-router",
411 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
412 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
413 | "project": "projects/828107101350",
414 | "versionedResources": [
415 | {
416 | "resource": {
417 | "creationTimestamp": "2024-07-01T09:16:25.814-07:00",
418 | "encryptedInterconnectRouter": false,
419 | "id": "3974532128576596150",
420 | "name": "ip-list-test-router",
421 | "nats": [
422 | {
423 | "enableEndpointIndependentMapping": false,
424 | "endpointTypes": [
425 | "ENDPOINT_TYPE_VM"
426 | ],
427 | "icmpIdleTimeoutSec": 30,
428 | "name": "ip-list-test-router-nat",
429 | "natIpAllocateOption": "MANUAL_ONLY",
430 | "natIps": [
431 | "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/addresses/ip-list-test-nat"
432 | ],
433 | "sourceSubnetworkIpRangesToNat": "ALL_SUBNETWORKS_ALL_IP_RANGES",
434 | "tcpEstablishedIdleTimeoutSec": 1200,
435 | "tcpTimeWaitTimeoutSec": 120,
436 | "tcpTransitoryIdleTimeoutSec": 30,
437 | "type": "PUBLIC",
438 | "udpIdleTimeoutSec": 30
439 | }
440 | ],
441 | "network": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/networks/public-ip-list-network",
442 | "region": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1",
443 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/regions/us-west1/routers/ip-list-test-router"
444 | },
445 | "version": "v1"
446 | }
447 | ]
448 | },
449 | {
450 | "additionalAttributes": {
451 | "address": "10.252.0.0"
452 | },
453 | "assetType": "compute.googleapis.com/Address",
454 | "createTime": "2024-07-01T16:16:01Z",
455 | "displayName": "ip-list-test-cloudsql-private",
456 | "location": "global",
457 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/addresses/ip-list-test-cloudsql-private",
458 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
459 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
460 | "project": "projects/828107101350",
461 | "state": "RESERVED",
462 | "versionedResources": [
463 | {
464 | "resource": {
465 | "address": "10.252.0.0",
466 | "addressType": "INTERNAL",
467 | "creationTimestamp": "2024-07-01T09:16:01.120-07:00",
468 | "description": "",
469 | "id": "6786608055203096750",
470 | "labelFingerprint": "42WmSpB8rSM=",
471 | "name": "ip-list-test-cloudsql-private",
472 | "network": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/networks/public-ip-list-network",
473 | "networkTier": "PREMIUM",
474 | "prefixLength": 16,
475 | "purpose": "VPC_PEERING",
476 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/addresses/ip-list-test-cloudsql-private",
477 | "status": "RESERVED"
478 | },
479 | "version": "v1"
480 | }
481 | ]
482 | },
483 | {
484 | "additionalAttributes": {
485 | "address": "34.54.244.120"
486 | },
487 | "assetType": "compute.googleapis.com/Address",
488 | "createTime": "2024-07-01T16:15:50Z",
489 | "displayName": "ip-list-test-static-address",
490 | "location": "global",
491 | "name": "//compute.googleapis.com/projects/fuzzy-pickles-428115/global/addresses/ip-list-test-static-address",
492 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
493 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
494 | "project": "projects/828107101350",
495 | "state": "IN_USE",
496 | "versionedResources": [
497 | {
498 | "resource": {
499 | "address": "34.54.244.120",
500 | "addressType": "EXTERNAL",
501 | "creationTimestamp": "2024-07-01T09:15:50.217-07:00",
502 | "description": "",
503 | "id": "7525013783512574169",
504 | "labelFingerprint": "42WmSpB8rSM=",
505 | "name": "ip-list-test-static-address",
506 | "networkTier": "PREMIUM",
507 | "selfLink": "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/addresses/ip-list-test-static-address",
508 | "status": "IN_USE",
509 | "users": [
510 | "https://www.googleapis.com/compute/v1/projects/fuzzy-pickles-428115/global/forwardingRules/ip-list-test-forwarding-rule-external-static"
511 | ]
512 | },
513 | "version": "v1"
514 | }
515 | ]
516 | },
517 | {
518 | "additionalAttributes": {
519 | "endpoint": "34.105.114.31"
520 | },
521 | "assetType": "container.googleapis.com/Cluster",
522 | "createTime": "2024-07-01T16:15:50Z",
523 | "displayName": "ip-list-test-cluster",
524 | "location": "us-west1",
525 | "name": "//container.googleapis.com/projects/fuzzy-pickles-428115/locations/us-west1/clusters/ip-list-test-cluster",
526 | "parentAssetType": "cloudresourcemanager.googleapis.com/Project",
527 | "parentFullResourceName": "//cloudresourcemanager.googleapis.com/projects/fuzzy-pickles-428115",
528 | "project": "projects/828107101350",
529 | "state": "RUNNING",
530 | "versionedResources": [
531 | {
532 | "resource": {
533 | "addonsConfig": {
534 | "gcePersistentDiskCsiDriverConfig": {
535 | "enabled": true
536 | },
537 | "kubernetesDashboard": {
538 | "disabled": true
539 | },
540 | "networkPolicyConfig": {
541 | "disabled": true
542 | }
543 | },
544 | "autopilot": {},
545 | "autoscaling": {
546 | "autoscalingProfile": "BALANCED"
547 | },
548 | "binaryAuthorization": {},
549 | "clusterIpv4Cidr": "10.84.0.0/14",
550 | "createTime": "2024-07-01T16:15:50+00:00",
551 | "currentMasterVersion": "1.29.4-gke.1043002",
552 | "currentNodeVersion": "1.29.4-gke.1043002",
553 | "databaseEncryption": {
554 | "currentState": "CURRENT_STATE_DECRYPTED",
555 | "state": "DECRYPTED"
556 | },
557 | "defaultMaxPodsConstraint": {
558 | "maxPodsPerNode": "110"
559 | },
560 | "endpoint": "34.105.114.31",
561 | "enterpriseConfig": {
562 | "clusterTier": "STANDARD"
563 | },
564 | "etag": "9b315a35-e894-45a3-8e76-844ecc43789c",
565 | "id": "b4fa9d0e8f524c73a5d3dc5e54a3490e51794b9e83b1417bb1c69fb87e49bae7",
566 | "initialClusterVersion": "1.29.4-gke.1043002",
567 | "initialNodeCount": 1,
568 | "ipAllocationPolicy": {
569 | "clusterIpv4Cidr": "10.84.0.0/14",
570 | "clusterIpv4CidrBlock": "10.84.0.0/14",
571 | "clusterSecondaryRangeName": "gke-ip-list-test-cluster-pods-b4fa9d0e",
572 | "podCidrOverprovisionConfig": {},
573 | "servicesIpv4Cidr": "34.118.224.0/20",
574 | "servicesIpv4CidrBlock": "34.118.224.0/20",
575 | "stackType": "IPV4",
576 | "useIpAliases": true
577 | },
578 | "labelFingerprint": "a9dc16a7",
579 | "legacyAbac": {},
580 | "location": "us-west1",
581 | "locations": [
582 | "us-west1-a",
583 | "us-west1-b",
584 | "us-west1-c"
585 | ],
586 | "loggingConfig": {
587 | "componentConfig": {
588 | "enableComponents": [
589 | "SYSTEM_COMPONENTS",
590 | "WORKLOADS"
591 | ]
592 | }
593 | },
594 | "loggingService": "logging.googleapis.com/kubernetes",
595 | "maintenancePolicy": {
596 | "resourceVersion": "e3b0c442"
597 | },
598 | "masterAuth": {
599 | "clientCertificateConfig": {},
600 | "clusterCaCertificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVMVENDQXBXZ0F3SUJBZ0lSQU82VlM5ODUyS2tKNERIWlMwa20zTW93RFFZSktvWklodmNOQVFFTEJRQXcKTHpFdE1Dc0dBMVVFQXhNa09HTmhOekl6TW1ZdE5tRXdaUzAwTjJSa0xXSTNOR1F0T1dZM1lXRTNaV0V4TVRjMApNQ0FYRFRJME1EY3dNVEUxTVRVMU1Wb1lEekl3TlRRd05qSTBNVFl4TlRVeFdqQXZNUzB3S3dZRFZRUURFeVE0ClkyRTNNak15WmkwMllUQmxMVFEzWkdRdFlqYzBaQzA1WmpkaFlUZGxZVEV4TnpRd2dnR2lNQTBHQ1NxR1NJYjMKRFFFQkFRVUFBNElCandBd2dnR0tBb0lCZ1FERWhZRlNUMVhyUzV3OHJ3TnQrdTh0NmY0TUxlL0pVR3RZSWJFKwp2a3BwUG1UVjFWTUJRNVNyT0pkY0pCR1grVEdRS1habVRyOW53NW9FNU02cDRFYmprQXFLcExrQ0kzN1NxVFN4CkprWWtzM3Q4MVpvdXpIQ09idWhEUjZzejlBOFAzcEU4NWpFaFhIcnNQUjl2ZlVWTyt5c3RuOEJDWFZaeDlOYTUKVXZvd2hOZUVETTIxd24rNmg4eU5qQ094bzdkeVNkRVRHUnZPYVg2YmRrTDFsbkZ6dkVwVVlaWXJaMGUwSXpnegptS28wTll3eEhqWG5LWTgwS3R1UmdzU1BsTVNrWnFqbGM0cmJtS3VGc05pelpnbVA1ZFM5VW5tRGlEcHZvOTI0CkVSR3g5aFZqbDhBajNyNmljaDlUSGQ0REUxbzhSZzR3cmtseWpNT2liSWpOWnNJYjhHc3FvS0I0a2VOOU9PZHQKUVI1MU5Id1c5ZGh6c1h0MG1lSlVwZzlNTGVMRWRFdDJjVHI4Y3dJQldNdWNjb3BsY0w5TUJIVHhkaDF0QTJuRQo1bHdHWE5MWTZsRC9QT2NBU1Z6ZzRwSlBSRU5EZmFHem4yTXJoSk81ZnEvRW5TQ2tLSjhpVEcxTmxWUnR6dmIyCkFwYzkzVHlhbGxQSGE3MkpKUVFHSW43YXp4MENBd0VBQWFOQ01FQXdEZ1lEVlIwUEFRSC9CQVFEQWdJRU1BOEcKQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGR2JtclFLdzMwZFhEejBLQWNyZkMrTVdUeVJHTUEwRwpDU3FHU0liM0RRRUJDd1VBQTRJQmdRQ2hyaG44eTYxT0FRY2JyY3FHMUl2R1pDYm80ck5tY2g4ZEVBQWJUdk8zCktLL05JZGVJNW01MlFMK1RZUW4yUGRxdXRLWEFFeXVWSHo2REVXUU4yQUVnUHExajZXV2N2dXd3RmlONEhITjAKbjNoWHRQc0xlTXhpYzBEY3gvSDlMbTI0UTMrekEvNG5yNy9JOEtZbWZObTZMN0dtRVhLbEs1U0NTclBKUWFjUQpYUHBYNDdtaFd1U0hGMWlTT1RwTHhTK1U4T1lwRzVCZDhHRytGdzRnZzlTOTNxQnpCeG0vaSsyK1l5ZDlmNlBXCm5kV2NjL2JVaWZkZ1puU0FtNU5lZTNSUmVoNXkvdk5SaldpMHFlMnpXTDV3WnZ4eUVZVStLRTVpdXRyOXpTcWMKY1FYck9iVEVnMng5eGoreFBkSUhoaFc5N3BwNlR6d091cFB5RDBFRFR3ZHgvd0p5ZnhXN1FlbW5zVUd6ZFBwcQpFTEp5NWhmLzNZQmlYRHpGdXRSNWZuQXJyUmRjcjA5NTZCVmdVK0VHZEFrNHlFa0xGMjlwTHl3L1BrU3o0UnBZCk8yVXI0K1ZOV09Md3B5TDc2SlhJRXpqSmxSdlo4OVUvcjZBWjdIQm1yeUpDVy9pZFBqWHYvekdtYkpGUmp1dVoKTTFWYzVhazBHMFJTVWoySEtmMlFnaXc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
601 | },
602 | "masterAuthorizedNetworksConfig": {
603 | "gcpPublicCidrsAccessEnabled": true
604 | },
605 | "monitoringConfig": {
606 | "advancedDatapathObservabilityConfig": {},
607 | "componentConfig": {
608 | "enableComponents": [
609 | "SYSTEM_COMPONENTS"
610 | ]
611 | },
612 | "managedPrometheusConfig": {
613 | "enabled": true
614 | }
615 | },
616 | "monitoringService": "monitoring.googleapis.com/kubernetes",
617 | "name": "ip-list-test-cluster",
618 | "network": "default",
619 | "networkConfig": {
620 | "defaultSnatStatus": {},
621 | "network": "projects/fuzzy-pickles-428115/global/networks/default",
622 | "serviceExternalIpsConfig": {},
623 | "subnetwork": "projects/fuzzy-pickles-428115/regions/us-west1/subnetworks/default"
624 | },
625 | "nodePoolDefaults": {
626 | "nodeConfigDefaults": {
627 | "loggingConfig": {
628 | "variantConfig": {
629 | "variant": "DEFAULT"
630 | }
631 | }
632 | }
633 | },
634 | "notificationConfig": {
635 | "pubsub": {}
636 | },
637 | "privateClusterConfig": {
638 | "privateEndpoint": "10.138.0.2",
639 | "publicEndpoint": "34.105.114.31"
640 | },
641 | "releaseChannel": {
642 | "channel": "REGULAR"
643 | },
644 | "securityPostureConfig": {
645 | "mode": "BASIC",
646 | "vulnerabilityMode": "VULNERABILITY_MODE_UNSPECIFIED"
647 | },
648 | "selfLink": "https://container.googleapis.com/v1/projects/fuzzy-pickles-428115/locations/us-west1/clusters/ip-list-test-cluster",
649 | "servicesIpv4Cidr": "34.118.224.0/20",
650 | "shieldedNodes": {
651 | "enabled": true
652 | },
653 | "status": "RUNNING",
654 | "subnetwork": "default",
655 | "zone": "us-west1"
656 | },
657 | "version": "v1"
658 | }
659 | ]
660 | }
661 | ]
662 |
--------------------------------------------------------------------------------
/pkg/output/api.go:
--------------------------------------------------------------------------------
1 | package output
2 |
3 | import (
4 | "encoding/csv"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 |
9 | "github.com/mark-adams/gcp-ip-list/pkg/gcp"
10 | "github.com/olekukonko/tablewriter"
11 | )
12 |
13 | type FormatterFunc func(w io.Writer, addresses []*gcp.Address) error
14 |
15 | func GetFormatters() map[string]FormatterFunc {
16 | return map[string]FormatterFunc{
17 | "csv": OutputCSV,
18 | "json": OutputJSON,
19 | "table": OutputTable,
20 | "list": OutputList,
21 | }
22 | }
23 |
24 | // OutputJSON outputs the addresses as a JSON array
25 | func OutputJSON(w io.Writer, addresses []*gcp.Address) error {
26 | enc := json.NewEncoder(w)
27 | addressWrapper := struct {
28 | Addresses []*gcp.Address `json:"addresses"`
29 | }{Addresses: addresses}
30 |
31 | if err := enc.Encode(addressWrapper); err != nil {
32 | return fmt.Errorf("error writing json: %w", err)
33 | }
34 | return nil
35 | }
36 |
37 | // OutputCSV outputs the addresses as a CSV with address, address_type, resource_type, and resource_name columns
38 | func OutputCSV(w io.Writer, addresses []*gcp.Address) error {
39 | records := [][]string{
40 | {"address", "address_type", "resource_type", "resource_name"},
41 | }
42 | for _, address := range addresses {
43 | records = append(records, []string{address.Address, address.AddressType, address.ResourceType, address.ResourceName})
44 | }
45 |
46 | cw := csv.NewWriter(w)
47 | if err := cw.WriteAll(records); err != nil {
48 | return fmt.Errorf("error writing csv: %w", err)
49 | }
50 |
51 | cw.Flush()
52 |
53 | if err := cw.Error(); err != nil {
54 | return fmt.Errorf("error writing csv: %w", err)
55 | }
56 |
57 | return nil
58 | }
59 |
60 | // OutputTable outputs the IP addresses as a table with Address, Address Type, Resource Type, and Resource Name columns
61 | func OutputTable(w io.Writer, addresses []*gcp.Address) error {
62 | table := tablewriter.NewWriter(w)
63 | table.SetHeader([]string{"Address", "Address Type", "Resource Type", "Resource Name"})
64 |
65 | for _, addr := range addresses {
66 | table.Append([]string{addr.Address, addr.AddressType, addr.ResourceType, addr.ResourceName})
67 | }
68 |
69 | table.Render()
70 |
71 | return nil
72 | }
73 |
74 | // OutputList outputs the IP addresses as a list, one per line
75 | func OutputList(w io.Writer, addresses []*gcp.Address) error {
76 | for _, addr := range addresses {
77 | _, err := fmt.Fprintf(w, "%s\n", addr.Address)
78 | if err != nil {
79 | return err
80 | }
81 | }
82 |
83 | return nil
84 | }
85 |
--------------------------------------------------------------------------------
/pkg/output/api_test.go:
--------------------------------------------------------------------------------
1 | package output_test
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/mark-adams/gcp-ip-list/pkg/gcp"
8 | "github.com/mark-adams/gcp-ip-list/pkg/output"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | var testAddresses = []*gcp.Address{
13 | {
14 | Address: "1.2.3.4",
15 | AddressType: gcp.AddressTypePublic,
16 | ResourceType: "compute.googleapis.com/Instance",
17 | ResourceName: "//compute.googleapis.com/instance-1",
18 | },
19 | {
20 | Address: "5.6.7.8",
21 | AddressType: gcp.AddressTypePublic,
22 | ResourceType: "sqladmin.googleapis.com/Instance",
23 | ResourceName: "//sqladmin.googleapis.com/instance-2",
24 | },
25 | }
26 |
27 | func TestOutputCSV(t *testing.T) {
28 | buf := bytes.NewBuffer(nil)
29 |
30 | err := output.OutputCSV(buf, testAddresses)
31 | require.NoError(t, err)
32 |
33 | output := buf.String()
34 | require.Equal(t, "address,address_type,resource_type,resource_name\n1.2.3.4,public,compute.googleapis.com/Instance,//compute.googleapis.com/instance-1\n5.6.7.8,public,sqladmin.googleapis.com/Instance,//sqladmin.googleapis.com/instance-2\n", output)
35 | }
36 |
37 | func TestOutputList(t *testing.T) {
38 | buf := bytes.NewBuffer(nil)
39 |
40 | err := output.OutputList(buf, testAddresses)
41 | require.NoError(t, err)
42 |
43 | output := buf.String()
44 | require.Equal(t, "1.2.3.4\n5.6.7.8\n", output)
45 | }
46 |
--------------------------------------------------------------------------------
/terraform/address.tf:
--------------------------------------------------------------------------------
1 | resource "google_compute_address" "default" {
2 | name = "${local.prefix}-public-not-used"
3 | address_type = "EXTERNAL"
4 | region = google_compute_router.default.region
5 | }
6 |
--------------------------------------------------------------------------------
/terraform/cloudsql.tf:
--------------------------------------------------------------------------------
1 | resource "google_sql_database_instance" "default" {
2 | name = "${local.prefix}-db"
3 | database_version = "POSTGRES_15"
4 | region = var.region
5 |
6 | settings {
7 | tier = "db-f1-micro"
8 |
9 | ip_configuration {
10 | ipv4_enabled = true
11 | private_network = google_compute_network.default.id
12 | }
13 | }
14 |
15 | depends_on = [google_service_networking_connection.cloudsql]
16 |
17 | deletion_protection = false
18 | }
19 |
20 | resource "google_compute_global_address" "cloudsql_private" {
21 | name = "${local.prefix}-cloudsql-private"
22 | purpose = "VPC_PEERING"
23 | address_type = "INTERNAL"
24 | prefix_length = 16
25 | network = google_compute_network.default.id
26 | }
27 |
28 | resource "google_service_networking_connection" "cloudsql" {
29 | network = google_compute_network.default.id
30 | service = "servicenetworking.googleapis.com"
31 | reserved_peering_ranges = [google_compute_global_address.cloudsql_private.name]
32 | }
33 |
--------------------------------------------------------------------------------
/terraform/enable_services.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This script is useful for enabling the required services in GCP before running `terraform apply` the first time
4 |
5 | gcloud services enable --project=$1 \
6 | cloudresourcemanager.googleapis.com \
7 | container.googleapis.com \
8 | compute.googleapis.com \
9 | servicenetworking.googleapis.com
--------------------------------------------------------------------------------
/terraform/kubernetes.tf:
--------------------------------------------------------------------------------
1 | resource "google_container_cluster" "default" {
2 | name = "${local.prefix}-cluster"
3 | location = var.region
4 |
5 | remove_default_node_pool = true
6 | initial_node_count = 1
7 |
8 | deletion_protection = false
9 | }
--------------------------------------------------------------------------------
/terraform/load-balancer-private.tf:
--------------------------------------------------------------------------------
1 | resource "google_compute_global_forwarding_rule" "internal" {
2 | name = "${local.prefix}-forwarding-rule-internal"
3 |
4 | depends_on = [google_compute_subnetwork.proxy_subnet_global]
5 | ip_protocol = "TCP"
6 | load_balancing_scheme = "INTERNAL_MANAGED"
7 | port_range = "80"
8 | target = google_compute_target_http_proxy.internal.id
9 |
10 | subnetwork = google_compute_subnetwork.lb_subnet.id
11 | }
12 |
13 | resource "google_compute_backend_service" "internal" {
14 | name = "${local.prefix}-backend-service-internal"
15 | enable_cdn = false
16 | timeout_sec = 10
17 | connection_draining_timeout_sec = 10
18 | load_balancing_scheme = "INTERNAL_MANAGED"
19 |
20 | backend {
21 | group = google_compute_network_endpoint_group.default.id
22 | balancing_mode = "RATE"
23 | max_rate = 1000
24 | }
25 |
26 | health_checks = [google_compute_health_check.default.id]
27 | }
28 |
29 | resource "google_compute_target_http_proxy" "internal" {
30 | name = "${local.prefix}-http-proxy-internal"
31 | url_map = google_compute_url_map.internal.id
32 | }
33 |
34 | # URL map
35 | resource "google_compute_url_map" "internal" {
36 | name = "${local.prefix}-url-map-internal"
37 | default_service = google_compute_backend_service.internal.id
38 | }
--------------------------------------------------------------------------------
/terraform/load-balancer-public-static.tf:
--------------------------------------------------------------------------------
1 | resource "google_compute_global_forwarding_rule" "external-static-ip" {
2 | name = "${local.prefix}-forwarding-rule-external-static"
3 |
4 | depends_on = [google_compute_subnetwork.proxy_subnet]
5 | ip_protocol = "TCP"
6 | load_balancing_scheme = "EXTERNAL_MANAGED"
7 | port_range = "80"
8 | target = google_compute_target_http_proxy.external.id
9 |
10 | ip_address = google_compute_global_address.default.id
11 | }
12 |
13 | resource "google_compute_global_address" "default" {
14 | name = "${local.prefix}-static-address"
15 | }
--------------------------------------------------------------------------------
/terraform/load-balancer-public.tf:
--------------------------------------------------------------------------------
1 | # This file creates a forwarding rule with a public IP
2 |
3 | resource "google_compute_global_forwarding_rule" "external" {
4 | name = "${local.prefix}-forwarding-rule-external"
5 |
6 | depends_on = [google_compute_subnetwork.proxy_subnet]
7 | ip_protocol = "TCP"
8 | load_balancing_scheme = "EXTERNAL_MANAGED"
9 | port_range = "80"
10 | target = google_compute_target_http_proxy.external.id
11 | }
12 |
13 | resource "google_compute_backend_service" "external" {
14 | name = "${local.prefix}-backend-service"
15 | enable_cdn = false
16 | timeout_sec = 10
17 | connection_draining_timeout_sec = 10
18 | load_balancing_scheme = "EXTERNAL_MANAGED"
19 |
20 | backend {
21 | group = google_compute_network_endpoint_group.default.id
22 | balancing_mode = "RATE"
23 | max_rate = 1000
24 | }
25 |
26 | health_checks = [google_compute_health_check.default.id]
27 | }
28 |
29 | resource "google_compute_target_http_proxy" "external" {
30 | name = "${local.prefix}-http-proxy"
31 | url_map = google_compute_url_map.external.id
32 | }
33 |
34 | # URL map
35 | resource "google_compute_url_map" "external" {
36 | name = "${local.prefix}-url-map"
37 | default_service = google_compute_backend_service.external.id
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/terraform/load-balancer-shared.tf:
--------------------------------------------------------------------------------
1 |
2 | resource "google_compute_network_endpoint_group" "default" {
3 | name = "${local.prefix}-neg"
4 | network = google_compute_network.default.id
5 | subnetwork = google_compute_subnetwork.lb_subnet.id
6 | default_port = "90"
7 | zone = "${var.region}-a"
8 | }
9 |
10 | resource "google_compute_health_check" "default" {
11 | name = "${local.prefix}-tcp-health-check"
12 |
13 | timeout_sec = 1
14 | check_interval_sec = 1
15 |
16 | tcp_health_check {
17 | port = "80"
18 | }
19 | }
--------------------------------------------------------------------------------
/terraform/locals.tf:
--------------------------------------------------------------------------------
1 | locals {
2 | prefix = "ip-list-test"
3 | }
--------------------------------------------------------------------------------
/terraform/nat-router.tf:
--------------------------------------------------------------------------------
1 | resource "google_compute_router" "default" {
2 | name = "${local.prefix}-router"
3 | region = google_compute_subnetwork.backend_subnet.region
4 | network = google_compute_network.default.id
5 | }
6 |
7 | resource "google_compute_router_nat" "nat" {
8 | name = "${local.prefix}-router-nat"
9 | router = google_compute_router.default.name
10 | region = google_compute_router.default.region
11 | nat_ip_allocate_option = "MANUAL_ONLY"
12 | source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
13 |
14 | nat_ips = [google_compute_address.nat.self_link]
15 | }
16 |
17 | resource "google_compute_address" "nat" {
18 | name = "${local.prefix}-nat"
19 | address_type = "EXTERNAL"
20 | region = google_compute_router.default.region
21 | }
22 |
--------------------------------------------------------------------------------
/terraform/network.tf:
--------------------------------------------------------------------------------
1 | resource "google_compute_network" "default" {
2 | name = "public-ip-list-network"
3 | auto_create_subnetworks = false
4 | }
5 |
6 | resource "google_compute_subnetwork" "proxy_subnet" {
7 | name = "proxy-subnet"
8 | ip_cidr_range = "10.0.1.0/24"
9 | region = var.region
10 | purpose = "REGIONAL_MANAGED_PROXY"
11 | role = "ACTIVE"
12 | network = google_compute_network.default.id
13 | }
14 |
15 | resource "google_compute_subnetwork" "proxy_subnet_global" {
16 | name = "proxy-subnet-global"
17 | ip_cidr_range = "10.0.4.0/24"
18 | purpose = "GLOBAL_MANAGED_PROXY"
19 | role = "ACTIVE"
20 | network = google_compute_network.default.id
21 | }
22 |
23 | resource "google_compute_subnetwork" "lb_subnet" {
24 | name = "lb-subnet"
25 | ip_cidr_range = "10.0.2.0/24"
26 | region = var.region
27 | network = google_compute_network.default.id
28 | }
29 |
30 | resource "google_compute_subnetwork" "backend_subnet" {
31 | name = "backend-subnet"
32 | ip_cidr_range = "10.0.3.0/24"
33 | region = var.region
34 | network = google_compute_network.default.id
35 | }
--------------------------------------------------------------------------------
/terraform/provider.tf:
--------------------------------------------------------------------------------
1 | provider "google" {
2 | project = var.project_id
3 | region = var.region
4 | }
--------------------------------------------------------------------------------
/terraform/vars.tf:
--------------------------------------------------------------------------------
1 | // variable for gcp project id
2 | variable "project_id" {
3 | description = "The GCP project ID"
4 | }
5 |
6 | variable "region" {
7 | description = "The GCP region"
8 | default = "us-west1"
9 | }
--------------------------------------------------------------------------------
/terraform/vm.tf:
--------------------------------------------------------------------------------
1 | resource "google_compute_instance" "default" {
2 | name = "${local.prefix}-vm"
3 | machine_type = "f1-micro"
4 | zone = "${var.region}-a"
5 |
6 | boot_disk {
7 | initialize_params {
8 | image = "debian-cloud/debian-12"
9 | }
10 | }
11 |
12 | network_interface {
13 | network = google_compute_network.default.id
14 | subnetwork = google_compute_subnetwork.backend_subnet.id
15 |
16 | access_config {
17 | // Ephemeral public IP
18 | }
19 | }
20 |
21 | deletion_protection = false
22 | }
--------------------------------------------------------------------------------