#!/usr/bin/env python3
"""Bucket versioning/lifecycle/IAM-condition recon (operator question 2026-08-14).

Per bucket visible to each LIVE SA (cfu-main 22, virtue-panakea 2,
innopharm-main 1): GET bucket metadata (versioning, lifecycle, retention,
logging, iamConfiguration), and where versioning=ON list object versions
(cap 100). Read-only GETs. Egress: storage.googleapis.com only.
Out: bucket_versioning_aug14.json + OPLOG.
"""
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')
DOSSIER = ROOT / 'redteam/gitlab_pharmalink_id'
KEYS_DIR = DOSSIER / 'gcp_keys'

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

def oplog(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 | storage.googleapis.com:443 | urllib/GET bucket metadata+versions | versioning/lifecycle/retention recon 25 buckets | {output} | {result} | none | read-only\n")

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

def load_sa(project_id):
    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('project_id') == project_id:
            return d
    return None

def mint_token(sa):
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    now = int(time.time())
    hdr = {'alg': 'RS256', 'typ': 'JWT', 'kid': sa['private_key_id']}
    cl = {'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(hdr).encode()) + b'.' + b64url(json.dumps(cl).encode())
    key = serialization.load_pem_private_key(sa['private_key'].encode(), 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'})
    return json.loads(body)['access_token'] if st == 200 else None

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

def main():
    out = {}
    for proj in ('cfu-main', 'virtue-panakea', 'innopharm-main'):
        sa = load_sa(proj)
        tok = mint_token(sa)
        buckets = json.loads((DOSSIER / 'gcp_scope_enum_aug14.json').read_text())[proj]['gcs_buckets']
        if not isinstance(buckets, list):
            continue
        proj_out = {}
        for b in buckets:
            st, meta = get_json(f'https://storage.googleapis.com/storage/v1/b/{b}'
                                f'?fields=name,versioning,lifecycle,retentionPolicy,logging,'
                                f'iamConfiguration,storageClass,timeCreated,updated,location', tok)
            entry = {'http': st}
            if st == 200:
                entry['versioning'] = meta.get('versioning', {}).get('enabled', False)
                lc = meta.get('lifecycle', {}).get('rule', [])
                entry['lifecycle_rules'] = [{'action': r.get('action', {}).get('type'),
                                             'age_days': r.get('condition', {}).get('age')} for r in lc]
                rp = meta.get('retentionPolicy')
                entry['retention'] = {'period_sec': rp.get('retentionPeriod'),
                                      'locked': rp.get('isLocked')} if rp else None
                entry['logging'] = meta.get('logging')
                entry['storageClass'] = meta.get('storageClass')
                entry['location'] = meta.get('location')
                entry['created'] = meta.get('timeCreated', '')[:10]
                # versions listing if enabled
                if entry['versioning']:
                    stv, vers = get_json(f'https://storage.googleapis.com/storage/v1/b/{b}/o'
                                         f'?versions=true&maxResults=100&fields=items(name,generation,size,updated,timeDeleted),nextPageToken', tok)
                    if stv == 200:
                        items = vers.get('items', [])
                        entry['versions_count_sample'] = len(items)
                        entry['deleted_versions'] = [{'n': i['name'], 's': int(i.get('size', 0)),
                                                      'deleted': i.get('timeDeleted', '')[:10]}
                                                     for i in items if i.get('timeDeleted')][:20]
                        entry['live_versions_sample'] = len([i for i in items if not i.get('timeDeleted')])
                    else:
                        entry['versions_error'] = f'http {stv}'
            else:
                entry['error'] = str(meta)[:150]
            proj_out[b] = entry
            print(f"  [{proj}/{b}] http={st} versioning={entry.get('versioning')} "
                  f"lifecycle={len(entry.get('lifecycle_rules', []))} retention={'Y' if entry.get('retention') else 'N'}",
                  file=sys.stderr)
        out[proj] = proj_out

    (DOSSIER / 'bucket_versioning_aug14.json').write_text(json.dumps(out, indent=1, ensure_ascii=False))
    v_on = sum(1 for p in out.values() for e in p.values() if e.get('versioning'))
    lc_on = sum(1 for p in out.values() for e in p.values() if e.get('lifecycle_rules'))
    ret = sum(1 for p in out.values() for e in p.values() if e.get('retention'))
    oplog(f'versioning_on={v_on} lifecycle={lc_on} retention={ret} -> bucket_versioning_aug14.json', 'SUCCESS')
    print(f'[+] versioning_on={v_on} lifecycle={lc_on} retention={ret}', file=sys.stderr)

if __name__ == '__main__':
    main()
