summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsubh <subh@example.com>2026-02-11 00:55:02 +0530
committersubh <subh@example.com>2026-02-11 00:55:02 +0530
commit6fdccecccd350df866a5365c7109591db5fc46e7 (patch)
tree729130ce7a3ee2cd2593386368c19e912dc39988
Initial Commit
-rw-r--r--README.md191
-rw-r--r--gcpsecretfinder.py165
-rw-r--r--requirements.txt2
3 files changed, 358 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..484c732
--- /dev/null
+++ b/README.md
@@ -0,0 +1,191 @@
+# GCP Secret Manager Scanner
+
+A Python tool for scanning Google Cloud Platform Secret Manager across all regions to discover and retrieve secrets. Useful for security audits, secret inventory, and cloud resource discovery.
+
+## Features
+
+- Scan all GCP regions for secrets in a single command
+- Multiple authentication methods (access token, token file, or service account key)
+- Optional secret value retrieval
+- Export results to JSON
+- Beautiful terminal UI with progress tracking
+- Quiet mode for script integration
+
+## Installation
+```bash
+git clone https://github.com/5epi0l/GCPSecretFinder.git
+cd GCPSecretFinder
+pip install -r requirements.txt
+```
+
+## Authentication
+
+The tool supports three authentication methods:
+
+### 1. Access Token (Direct)
+```bash
+python3 gcpsecretfinder.py --project my-project --token "ya29.c.b0Aaek..."
+```
+
+### 2. Access Token (From File)
+```bash
+python3 gcpsecretfinder.py --project my-project --token-file ~/token.txt
+```
+
+### 3. Service Account Key
+```bash
+python3 gcpsecretfinder.py --project my-project --service-account-key ~/key.json
+```
+
+## Usage Examples
+
+### Basic Scanning
+
+List all secrets across all regions:
+```bash
+python3 gcpsecretfinder.py --project gr-proj-8 --token-file ~/token.txt
+```
+
+### Retrieve Secret Values
+
+Scan and retrieve the actual secret values:
+```bash
+python3 gcpsecretfinder.py --project my-project --token-file ~/token.txt --retrieve
+```
+
+### Retrieve Specific Version
+
+Get a specific version of secrets instead of latest:
+```bash
+python3 gcpsecretfinder.py --project my-project \
+ --token-file ~/token.txt \
+ --retrieve \
+ --version 2
+```
+
+## Command-Line Options
+```
+required arguments:
+ -p, --project PROJECT GCP project ID
+
+authentication (one required):
+ -t, --token TOKEN Access token (String)
+ -f, --token-file FILE Path to file containing access token
+ -s, --service-account-key Path to service account key file
+
+optional arguments:
+ --retrieve Retrieve secret values (not just list them)
+ --version VERSION Secret version to retrieve (default: latest)
+ -h, --help Show help message
+```
+
+## Supported Regions
+
+The tool scans the following regions by default:
+
+**Asia Pacific:**
+- asia-east1, asia-east2
+- asia-northeast1, asia-northeast2, asia-northeast3
+- asia-south1, asia-south2
+- asia-southeast1, asia-southeast2
+- australia-southeast1, australia-southeast2
+
+**Europe:**
+- europe-central2
+- europe-north1
+- europe-west1, europe-west2, europe-west3, europe-west4, europe-west6
+
+**Middle East:**
+- me-central1, me-west1
+
+**North America:**
+- northamerica-northeast1, northamerica-northeast2
+- us-central1
+- us-east1, us-east4
+- us-west1, us-west2, us-west3, us-west4
+
+**South America:**
+- southamerica-east1, southamerica-west1
+
+## Output Format
+
+
+### JSON Output
+
+With `--retrieve`, secrets are retrieved in JSON format:
+```json
+[
+ {
+ "name": "projects/123456/secrets/api-key",
+ "region": "us-east1",
+ "value": "sk-abc123...",
+ "version": "latest",
+ "full_data": {
+ "name": "projects/123456/secrets/api-key",
+ "createTime": "2024-01-15T10:30:00Z",
+ "labels": {},
+ "replication": {
+ "automatic": {}
+ }
+ }
+ }
+]
+```
+
+## Common Use Cases
+
+### Security Audit
+```bash
+# Find all secrets and retrieve them
+python3 gcpsecretfinder.py \
+ --project prod-project \
+ --service-account-key ~/audit-sa.json \
+ --retrieve
+```
+
+
+### Secret Inventory
+```bash
+# List all secrets without retrieving values
+python3 gcpsecretfinder.py \
+ --project my-project \
+ --token-file ~/token.txt \
+```
+
+### Integration with Other Tools
+```bash
+# Pipe to jq for filtering
+python3 gcpsecretfinder.py \
+ --project my-project \
+ --token-file ~/token.txt \
+ --retrieve \
+ | jq '.[] | select(.name | contains("production"))'
+```
+
+## Permissions Required
+
+The service account or user must have the following IAM permissions:
+
+- `secretmanager.secrets.list` - To list secrets
+- `secretmanager.versions.access` - To retrieve secret values (when using `--retrieve`)
+
+
+## Error Handling
+
+The tool handles common errors gracefully:
+- Network timeouts (10 second timeout per request)
+- Invalid credentials
+- Missing permissions
+- Non-existent regions
+- Failed secret retrieval
+
+Errors are logged to stderr while scanning continues for remaining regions.
+
+## Performance Considerations
+
+- Each region is scanned sequentially to avoid rate limiting
+- Default timeout is 10 seconds per region
+- Scanning all 31 regions typically takes 30-60 seconds
+- Retrieval adds additional time proportional to number of secrets found
+
+
diff --git a/gcpsecretfinder.py b/gcpsecretfinder.py
new file mode 100644
index 0000000..b6c2cf7
--- /dev/null
+++ b/gcpsecretfinder.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+
+
+import argparse
+import requests
+from google.oauth2 import service_account
+from typing import List, Optional, Tuple
+import json
+from google.auth.transport.requests import Request
+import sys
+
+REGIONS = [
+ "us-west1","us-west2", "us-west3", "us-west4", "us-central1", "us-east1", "us-east4", "us-east5", "us-south1", "northamerica-northeast1", "northamerica-northeast2", "southamerica-west1", "southamerica-east1", "northamerica-south1", "europe-west2", "europe-west1", "europe-west4", "europe-west6", "europe-west3", "europe-central2", "europe-west8", "europe-southwest1", "europe-west9", "europe-west12", "europe-west10", "europe-north2", "asia-south1", "asia-south2", "asia-southeast1", "asia-southeast2", "asia-east2", "asia-east1", "asia-northeast1", "asia-northeast2", "australia-southeast1", "australia-southeast2", "asia-northeast3", "asia-southeast3", "me-west1", "me-central1", "me-central2", "africa-south1"
+ ]
+
+
+def getAccessTokenForServiceAccount(key_file: str) -> str:
+ credentials = service_account.Credentials.from_service_account_file(
+ key_file,
+ scopes=['https://www.googleapis.com/auth/cloud-platform']
+ )
+ credentials.refresh(Request())
+ return credentials.token()
+
+
+def scanRegionsForSecrets(project: str, region: str, access_token: str) -> Optional[List[str]]:
+
+ url = f"https://secretmanager.{region}.rep.googleapis.com/v1/projects/{project}/locations/{region}/secrets"
+ headers = {
+ "Authorization": f"Bearer {access_token}"
+ }
+
+ try:
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+
+ data = response.json()
+ secrets = data.get('secrets', [])
+
+ if secrets:
+ return [secret.get('name') for secret in secrets if secret.get('name')]
+ return []
+
+
+ except requests.exceptions.RequestException as e:
+ print(f"[!] An error has occured: {e}", file=sys.stderr)
+
+def retrieveFoundSecrets(secret_name: str, access_token: str, version: str = "latest") -> Optional[Tuple[str, bool]]:
+ """
+ Args:
+ secret_name: Full secret name
+ access_token: GCP access Token
+ Version: Version to retrieve (default: "latest")
+ """
+
+ region = secret_name.split('/')[3]
+ project_num = secret_name.split('/')[1]
+ secret = secret_name.split('/')[5]
+ url = f"https://secretmanager.{region}.rep.googleapis.com/v1/projects/{project_num}/locations/{region}/secrets/{secret}/versions/latest:access"
+ headers = {
+ "Authorization": f"Bearer {access_token}"
+ }
+
+ try:
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status()
+
+ data = response.json()
+ payload = data.get('payload', {})
+
+
+ if 'data' in payload:
+ import base64
+ decoded = base64.b64decode(payload['data']).decode('utf-8')
+ return decoded, False
+
+ return None, False
+
+
+ except requests.exceptions.RequestException as e:
+ print(f"[!] Error retrieving secret: {secret_name}: {e}", file=sys.stderr)
+ return None, False
+
+def main():
+
+ parser = argparse.ArgumentParser(
+ description='Enumerating GCP Secrets Across All Locations',
+ formatter_class = argparse.RawDescriptionHelpFormatter
+ )
+ parser.add_argument(
+ '-p','--project', required=True, help='GCP Project ID'
+ )
+ parser.add_argument(
+ '-t','--token', help='Access Token (String)'
+ )
+ parser.add_argument(
+ '-f','--token-file', help='Access Token File'
+ )
+ parser.add_argument(
+ '-s', '--service-account-key',help='Service Account Key JSON File'
+ )
+
+ parser.add_argument(
+ '-r', '--retrieve', action='store_true', help='Retrieve Secret Values'
+ )
+
+ parser.add_argument(
+ '-v', '--version', default='latest', help='Secret Version to retrieve. (default: Latest)'
+ )
+
+ args = parser.parse_args()
+
+ if args.token:
+ access_token = args.token
+ elif args.token_file:
+ try:
+ with open(args.token_file, 'r') as f:
+ access_token = f.read().strip()
+ except IOError as e:
+ print(f"[!] Error occured while reading token from file: {e}", file=sys.stderr)
+ sys.exit(1)
+ else:
+ try:
+ access_token = get_access_token_from_service_account(args.service_account_key)
+ except Exception as e:
+ print(f"[!] Error occured while getting token from service Account: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+ regions_to_scan = REGIONS
+
+
+ all_secrets = []
+ total_secrets = 0
+ secretName = None
+
+ print("[*] Hunting Secrets...")
+
+ for region in regions_to_scan:
+ secrets = scanRegionsForSecrets(args.project, region, access_token)
+
+ if secrets:
+ for secret in secrets:
+ if args.retrieve:
+ value, is_binary = retrieveFoundSecrets(secret, access_token, args.version)
+ if value is not None:
+ secretName = value
+
+
+ all_secrets.append(secretName)
+
+ print(f"[*] secret found: {secret}")
+ total_secrets += len(secrets)
+
+ if args.retrieve:
+ print("[*] Retrieving Value")
+ if all_secrets:
+ for s in all_secrets:
+ print(f"\n{s}")
+
+ if total_secrets == 0:
+ print("[!] No Secrets Found")
+
+if __name__ == "__main__":
+ main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..b5d334d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,2 @@
+google-auth
+requests