#!/usr/bin/env python3
"""Jenkins discovery — passive DNS/HTTP probe (operator GO 2026-08-14).

1) Extract host references from cloned k8s-env repos + L2 blob hits
   (chartmuseum, jenkins, nexus, harbor, sonar, vault, k8s API endpoints).
2) DNS-resolve candidate names (*.pharmalink.id + found internal names via
   public resolver only — no internal zone queries from victim infra).
3) HTTP(S) GET probes on resolved hosts: /, /login, headers only. Identify
   Jenkins by X-Jenkins header / login page markers. No auth attempts.

Egress: DNS (system resolver), HTTP(S) GETs to found hosts. Read-only.
Out: jenkins_discovery_aug14.json + OPLOG.
"""
import importlib.util
import json
import re
import socket
import ssl
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

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

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)

CTX = ssl.create_default_context(); CTX.check_hostname = False; CTX.verify_mode = ssl.CERT_NONE

def oplog(dst, 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} | socket/urllib | DNS resolve + GET / /login | Jenkins passive discovery | {output} | {result} | none | passive\n")

CANDIDATES = [
    'jenkins.pharmalink.id', 'ci.pharmalink.id', 'cd.pharmalink.id',
    'build.pharmalink.id', 'devops.pharmalink.id', 'devsecops.pharmalink.id',
    'chartmuseum.pharmalink.id', 'charts.pharmalink.id', 'nexus.pharmalink.id',
    'harbor.pharmalink.id', 'registry.pharmalink.id', 'sonar.pharmalink.id',
    'sonarqube.pharmalink.id', 'argocd.pharmalink.id', 'argo.pharmalink.id',
    'grafana.pharmalink.id', 'kibana.pharmalink.id', 'vault.pharmalink.id',
    'gitlab.pharmalink.id', 'k8s.pharmalink.id', 'rancher.pharmalink.id',
    'minio.pharmalink.id', 's3.pharmalink.id', 'openkm.pharmalink.id',
    'openobserve.pharmalink.id', 'o2.pharmalink.id',
]

def resolve(name):
    try:
        infos = socket.getaddrinfo(name, None, socket.AF_INET)
        return sorted({i[4][0] for i in infos})
    except socket.gaierror:
        return []
    except Exception:
        return []

def http_probe(host):
    out = {}
    for scheme in ('https', 'http'):
        for path in ('/', '/login'):
            url = f'{scheme}://{host}{path}'
            try:
                req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
                with urllib.request.urlopen(req, timeout=10, context=CTX) as r:
                    body = r.read(4096).decode('utf-8', 'ignore')
                    hdrs = dict(r.headers)
                    out[f'{scheme}{path}'] = {
                        'status': r.status,
                        'server': hdrs.get('Server'),
                        'x_jenkins': hdrs.get('X-Jenkins'),
                        'x_hudson': hdrs.get('X-Hudson'),
                        'www_auth': hdrs.get('WWW-Authenticate'),
                        'location': hdrs.get('Location'),
                        'title': (re.search(r'<title[^>]*>([^<]{0,120})', body, re.I) or [None, None])[1],
                        'jenkins_marker': bool(re.search(r'jenkins|hudson', body, re.I)),
                    }
            except urllib.error.HTTPError as e:
                hdrs = dict(e.headers) if e.headers else {}
                out[f'{scheme}{path}'] = {'status': e.code, 'server': hdrs.get('Server'),
                                          'x_jenkins': hdrs.get('X-Jenkins'),
                                          'www_auth': hdrs.get('WWW-Authenticate'),
                                          'location': hdrs.get('Location')}
            except Exception as e:
                out[f'{scheme}{path}'] = {'error': f'{type(e).__name__}'}
    return out

def repo_host_intel():
    hits = set()
    for f in (DOSSIER / 'repos').rglob('*'):
        if not f.is_file() or '.git' in f.parts:
            continue
        try:
            txt = f.read_text(errors='ignore')
        except Exception:
            continue
        for m in re.finditer(r'(?:https?://)?([a-z0-9][a-z0-9.-]*(?:pharmalink\.id|chartmuseum|jenkins|nexus|harbor|sonar|vault|grafana|kibana)[a-z0-9.-]*(?::\d+)?)', txt, re.I):
            hits.add(m.group(1).lower())
    return sorted(hits)

def main():
    report = {'repo_host_refs': repo_host_intel(), 'dns': {}, 'probes': {}}
    print(f"[*] repo host refs: {report['repo_host_refs']}", file=sys.stderr)

    # resolve candidates + repo-derived pharmalink.id names
    names = set(CANDIDATES)
    for h in report['repo_host_refs']:
        if h.endswith('pharmalink.id'):
            names.add(h.split(':')[0])
    for name in sorted(names):
        ips = resolve(name)
        if ips:
            report['dns'][name] = ips
            print(f'  [dns] {name} -> {ips}', file=sys.stderr)

    # probe resolved (skip gitlab/vault — known)
    for name, ips in sorted(report['dns'].items()):
        if name in ('gitlab.pharmalink.id', 'vault.pharmalink.id'):
            continue
        report['probes'][name] = http_probe(name)
        # compact verdict
        verdicts = []
        for k, v in report['probes'][name].items():
            if isinstance(v, dict):
                s = v.get('status', v.get('error'))
                mark = []
                if v.get('x_jenkins'):
                    mark.append(f"X-Jenkins={v['x_jenkins']}")
                if v.get('jenkins_marker'):
                    mark.append('jenkins-in-body')
                if v.get('title'):
                    mark.append(f"title={v['title'][:40]}")
                verdicts.append(f'{k}={s} {";".join(mark)}')
        print(f'  [probe] {name}: {verdicts}', file=sys.stderr)

    (DOSSIER / 'jenkins_discovery_aug14.json').write_text(json.dumps(report, indent=1, ensure_ascii=False))
    jenkins_hits = [n for n, p in report['probes'].items()
                    if any(isinstance(v, dict) and (v.get('x_jenkins') or v.get('jenkins_marker'))
                           for v in p.values())]
    oplog('dns+resolved hosts', f'resolved={len(report["dns"])} jenkins_markers={jenkins_hits}',
          'SUCCESS' if jenkins_hits else 'PARTIAL')
    print(f'[+] jenkins markers: {jenkins_hits}', file=sys.stderr)

if __name__ == '__main__':
    main()
