#!/usr/bin/env python3 """Build a real Dockerfile and export its image through a dedicated Docker API. Only the trusted build step receives the socket. Scan steps receive files only. The explicit demo context allows Dockerfile, src/ and fixtures/; it never sends Git credentials, reports or arbitrary checkout files to the image builder. """ import hashlib import http.client import io import json import os from pathlib import Path import socket import tarfile import urllib.parse class Docker(http.client.HTTPConnection): def __init__(self): super().__init__('localhost', timeout=900) def connect(self): self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.settimeout(self.timeout) self.sock.connect('/var/run/docker.sock') def request(method, path, body=None, headers=None): c = Docker(); c.request(method, '/v1.41'+path, body, headers or {}) r = c.getresponse() if r.status >= 300: raise RuntimeError(f'Docker API {method} {path}: HTTP {r.status}: {r.read(4096)!r}') return c,r out = Path('/artifacts'); out.mkdir(exist_ok=True) archive = out/'app.tar' if archive.exists(): raise RuntimeError('Refusing to reuse an existing candidate archive') context = io.BytesIO() with tarfile.open(fileobj=context, mode='w') as t: for name in ['Dockerfile', 'src', 'fixtures']: for p in ([Path(name)] if Path(name).is_file() else sorted(Path(name).rglob('*'))): if p.is_symlink(): raise RuntimeError(f'Symlinks are not permitted in the demo context: {p}') if p.is_file(): t.add(p, arcname=str(p), recursive=False) tag='localhost/devsecops/my-app:build-'+os.environ.get('DRONE_BUILD_NUMBER','local') query=urllib.parse.urlencode({'t':tag,'dockerfile':'Dockerfile','pull':'false','networkmode':'none','rm':'true'}) c,r=request('POST','/build?'+query,context.getvalue(),{'Content-Type':'application/x-tar'}) for line in r: if not line.strip(): continue event=json.loads(line) if 'error' in event or 'errorDetail' in event: raise RuntimeError(event) if 'stream' in event: print(event['stream'],end='',flush=True) c.close() c,r=request('GET','/images/'+urllib.parse.quote(tag,safe='')+'/json') identity=json.load(r); c.close() c,r=request('GET','/images/'+urllib.parse.quote(tag,safe='')+'/get') h=hashlib.sha256() with (out/'app.tar.part').open('wb') as f: while chunk:=r.read(1024*1024): f.write(chunk); h.update(chunk) c.close() (out/'app.tar.part').replace(archive) assert archive.stat().st_size > 0 manifest={'image':tag,'image_id':identity['Id'],'sha256':h.hexdigest(), 'commit':os.environ.get('DRONE_COMMIT_SHA'), 'build':os.environ.get('DRONE_BUILD_NUMBER')} (out/'build.json').write_text(json.dumps(manifest,indent=2)+'\n') print(json.dumps(manifest),flush=True)