├── .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 | [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/mark-adams/gcp-ip-list) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/mark-adams/gcp-ip-list/main/LICENSE) [![Build Status](https://github.com/mark-adams/gcp-ip-list/actions/workflows/test.yml/badge.svg)](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 | 17 | 20 | 21 |
Homebrew (macOS or Linux) 18 | brew tap mark-adams/gcp-ip-list && brew install gcp-ip-list 19 |
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 | } --------------------------------------------------------------------------------