Harness Academy
Tài liệu gốc 34 11 phútTiếng Việt + English
docs/notes/credentials_proxy.mdXem bản gốc
Ngôn ngữ bài đọc
Bản dịch tiếng Việt

Bản dịch đầy đủ theo đúng cấu trúc tài liệu; code, liên kết và định danh kỹ thuật được giữ nguyên.

Proxy thông tin xác thực

Đã xóa trong cam kết 51d7457 (PR #32), mỗi ADR-0016. Đọc nó tại git show 51d7457^:src/decode/sandbox/proxy.py. Ghi chú này giải thích nó là gì và nó hoạt động như thế nào, bởi vì ý tưởng đáng được giữ lại mặc dù mã thì không.

Phiên bản một câu

Vùng chứa Worker được đóng hộp cát có thể thực hiện lệnh gọi được xác thực tới GitHub trong khi giữ no thông tin xác thực: nó đã gửi yêu cầu mà không có token và một thùng chứa mitmproxy nằm trên cùng một mạng docker được đính kèm Authorization tiêu đề sau yêu cầu đã rời khỏi Công nhân.

Thuộc tính mà nó đã mua: quy trình chạy các lệnh shell do mô hình chọn không thể đọc được token, vì token không có trong môi trường, hệ thống tệp hoặc bộ nhớ của nó.

Phạm vi - nơi áp dụng

TrụcGiá trị
Bề mặtChỉ mất đầu (decode run / decode replay). REPL chưa bao giờ xây dựng nó - đó là những gì được giữ nguyên decode từ việc nhập khẩu kitaru.
Chế độ hộp cátdocker chỉ. modal chưa bao giờ có nó (không có mạng docker để tắt bộ chứa proxy) - thay vào đó, nó đã chèn trực tiếp token (ADR-0012 §10). none là chủ nhà nên không có gì để bảo vệ.
Mặc địnhTắt. DEFAULT_PROXY_RULES đã vận chuyển trống; một bản đồ thông tin xác thực trống là một proxy chuyển tiếp không có tác dụng gì.
Đã đính hôn khiSANDBOX_MODE=docker và (SANDBOX_CREDENTIAL_PROXY_ENABLED=true hoặc không trống SANDBOX_GIT_TOKEN).

Chiếc bàn đó cũng chính là văn bia: tính bất biến mà nó bảo vệ là đúng một trong ba các chế độ — chứ không phải chế độ điều khiển từ xa, nơi mà sự cách ly là quan trọng nhất.

Kiến trúc

flowchart TB
    subgraph host["HOST — the decode process (headless flow)"]
        settings["Settings (hydrated)<br/>SANDBOX_GIT_TOKEN · SANDBOX_PROXY_IMAGE"]
        rules["Proxy Rules<br/>SandboxProxyRule(name, hosts[], headers{})<br/>DEFAULT_PROXY_RULES = [] · github_token_rules(token)"]
        cmap["build_credential_map(rules)<br/>resolve templates HOST-SIDE, at flow start<br/>→ {host: {header: value}}"]
        sp["_sandbox_proxy() context manager<br/>start → install executor → yield → teardown"]
        settings --> rules --> cmap --> sp
    end

    subgraph net["per-run docker network — decode-sandbox-net-&lt;uuid12&gt;"]
        worker["WORKER container (decode-sandbox-&lt;id&gt;)<br/>image: uv:python3.12-bookworm-slim<br/>/workspace = bind-mount of .decode/sandbox<br/>env: http_proxy/https_proxy → proxy · GH_TOKEN=&lt;decoy&gt;<br/>trust store: + mitmproxy CA<br/><b>HOLDS NO REAL CREDENTIAL</b>"]
        proxy["PROXY container (decode-proxy-&lt;uuid12&gt;)<br/>image: mitmproxy/mitmproxy<br/>mitmdump --listen-port 8080 -s /opt/proxy_addon.py<br/>env: DECODE_CREDENTIAL_MAP={host:{header:value}}<br/><b>HOLDS THE CREDENTIAL</b>"]
    end

    internet([internet — github.com · api.github.com · everything else])

    sp -- "docker network create" --> net
    sp -- "docker run --env-file (0600, unlinked)" --> proxy
    sp -- "docker run --network + proxy env + CA mount" --> worker
    worker -- "request, NO auth header" --> proxy
    proxy -- "request + injected Authorization" --> internet
    internet -- "response (unmodified)" --> proxy -- "response" --> worker

    classDef hostc fill:#1565c0,stroke:#0d47a1,color:#fff;
    classDef sec fill:#e65100,stroke:#bf360c,color:#fff;
    classDef ext fill:#6a1b9a,stroke:#38006b,color:#fff;
    class settings,rules,cmap,sp hostc;
    class worker,proxy sec;
    class internet ext;

Ba quy trình, ba mức độ tin cậy: máy chủ biết bí mật và giải quyết nó; proxy giữ bí mật đã được giải quyết trong môi trường riêng của nó; Worker — người duy nhất chạy mã do model chọn — không giữ được gì cả.

Đầu vào và đầu ra

Đầu vào (tất cả phía máy chủ):

Đầu vàoTừ đâu đếnVai trò
SANDBOX_MODE=dockerSettingsCổng. Bất cứ điều gì khác = không có proxy.
SANDBOX_CREDENTIAL_PROXY_ENABLEDSettings (bool, mặc định false)Chọn tham gia rõ ràng cho đường dẫn quy tắc.
SANDBOX_GIT_TOKENSettings (SecretStr)Phím tắt GitHub một nút bấm — một giá trị không trống được tự động tương tác proxy.
SANDBOX_PROXY_IMAGESettings (mặc định mitmproxy/mitmproxy)Hình ảnh vùng chứa addon.
DEFAULT_PROXY_RULESMã không đổi, đã vận chuyển []Quy tắc dành cho bất kỳ máy chủ khác nào.

Tạo tác trung gian — bản đồ thông tin xác thực, đối tượng duy nhất chứa bí mật đã được giải quyết:

# build_credential_map(rules) → this, host-side, at flow start
{
  "api.github.com": {"Authorization": "Bearer ghp_xxx"},           # REST API (gh, curl)
  "github.com":     {"Authorization": "Basic eC1hY2Nlc3MtdG9rZW46Z2hwX3h4eA=="},  # git push over HTTPS
}

Hai quy tắc cho một token, vì GitHub muốn xác thực khác cho hai lần vận chuyển: Bearer đối với API REST, Basic base64("x-access-token:<PAT>") đối với git-over-HTTPS (từ chối Bearer). api.github.com phải đến đầu tiên — tiện ích bổ sung trả về kết quả khớp đầu tiên và github.com trận đấu cha mẹ api.github.com.

Đầu ra (cái gì _sandbox_proxy() được tạo ra cho phần còn lại của luồng):

Đầu raNgười tiêu dùng
proxy.network — decode-sandbox-net-<uuid12>DockerBackend(network=…) - Công nhân tham gia vào nó.
proxy.worker_proxy_env — http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY = http://decode-proxy-<uuid12>:8080, no_proxy=localhost,127.0.0.1, GH_TOKEN=<decoy>DockerBackend(proxy_env=…) — -e cờ trên Công nhân docker run. Mang theo không có bí mật.
proxy.ca_cert_host_path — CA mitmproxy trong thư mục tạm thời của máy chủDockerBackend(ca_cert_host_path=…) — chỉ đọc được gắn liên kết vào Worker, sau đó được tin cậy.
A SandboxExecutor nối vào đó DockerBackendinstall_executor() - trở thành bashngười thực thi cho nhịp luồng.

Cách thông tin xác thực tiếp cận proxy — và chỉ proxy

Bản đồ đã giải quyết không bao giờ chạm vào Công nhân và không bao giờ chạm vào bản đồ của chủ nhà argv hoặc:

  1. build_credential_map() giải quyết các quy tắc phía máy chủ thành {host: {header: value}}.
  2. DockerCredentialProxy._run_proxy_container() ghi bản đồ đó dưới dạng JSON vào 0600 tập tin tạm thời (DECODE_CREDENTIAL_MAP=<json>), chuyển nó dưới dạng docker run --env-file <path>, và unlinkđó là tập tin trong một finally khoảnh khắc docker run trở lại. Tuổi thọ trên đĩa: mili giây. Nó không bao giờ xuất hiện ở docker rundòng lệnh của, do đó phía máy chủ ps không bao giờ nhìn thấy nó.
  3. Bên trong thùng chứa, proxy_addon.py đọc nó một lần khi nhập: json.loads(os.environ.get("DECODE_CREDENTIAL_MAP", "{}")).
  4. Nhật ký ở mọi nơi đều in tên tiêu đề và tên máy chủ, không bao giờ có giá trị.

của công nhân docker run có --network, các biến env URL proxy và một giá trị CA. Không có bí mật.

Đường dẫn yêu cầu — Worker → internet

sequenceDiagram
    participant W as Worker container<br/>(git / gh / curl / urllib)
    participant P as mitmproxy + CredentialAddon
    participant G as github.com

    Note over W: env: https_proxy=http://decode-proxy-xxx:8080<br/>trust store contains the mitmproxy CA
    W->>P: CONNECT github.com:443
    P-->>W: 200 Connection established
    W->>P: TLS handshake — P serves a leaf cert<br/>signed by the mitmproxy CA (Worker trusts it → OK)
    W->>P: POST /decode/decode.git/git-receive-pack<br/>(NO Authorization header)
    Note over P: addon.request(flow)<br/>_match_host("github.com") → hit<br/>flow.request.headers["Authorization"] = "Basic …"<br/>(SET, not append — overwrites the gh decoy)
    P->>G: TLS to the real github.com (upstream, normal certs)<br/>POST … + Authorization: Basic …
    G-->>P: 200 (push accepted)
    P-->>W: 200 (response passed through UNMODIFIED)

Những điểm quan trọng:

  • TLS bị chấm dứt hai lần. mitmproxy được thiết kế theo kiểu trung gian: nó cung cấp cho Công nhân một chứng chỉ lá do CA của chính nó tạo ra (đó là lý do tại sao CA phải nằm trong kho ủy thác của Người lao động), và mở một kết nối TLS thông thường *riêng biệt ngược dòng. Nó đọc và viết lại bản rõ trong giữa. Đó là toàn bộ thủ thuật - bạn không thể đưa tiêu đề vào kết nối mà bạn không thể decode.
  • Phần bổ sung chỉ chạm vào yêu cầu. request móc đặt tiêu đề; không có response cái móc. Internet → Công nhân là một người truyền qua.
  • headers[name] = value thay thế. Đó là lý do để token mồi nhử hoạt động (bên dưới).
  • Máy chủ bỏ lỡ là vượt qua, không phải thất bại. cli.github.com (cái gh cài đặt apt) không khớp quy tắc, vì vậy nó thoát ra mà không được tiêm. _match_host khớp chính xác hoặc tên miền gốc, không phân biệt chữ hoa chữ thường.

token mồi nhử

gh từ chối đưa ra yêu cầu bất kỳ nào khi nó không tìm thấy token nào trong môi trường - nó bị lỗi cục bộ với "gh auth login" và không bao giờ tiếp cận được proxy đã xác thực nó. Thế là Người Công Nhân được giao một trình giữ chỗ:

_GH_PLACEHOLDER_TOKEN = "decode-proxy-injects-the-real-token"   # GH_TOKEN in the worker env

gh sau đó vui vẻ tiến hành, gửi Authorization: token decode-proxy-injects-the-real-token, và addon ghi đè tiêu đề đó bằng thông tin xác thực thực sau khi yêu cầu rời khỏi Worker. các chuỗi không xác thực gì cả; sự bất biến tồn tại. Đây cũng là lời nói rõ ràng nhất rằng thiết kế đang đấu tranh với môi trường của nó.

Niềm tin của container CA - cuộc đua cần khắc phục đồng bộ

Worker phải tin cậy CA của proxy trước khi lệnh đầu tiên chạy hoặc lệnh đầu tiên git/curl sẽ thất bại khi xác thực chứng chỉ. Vì vậy DockerBackend.create():

  1. Liên kết gắn CA chỉ đọc tại /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt - chính xác con đường update-ca-certificates gấp vào repository tin cậy của hệ thống.
  2. Chạy docker exec <worker> update-ca-certificates đồng bộ, đang chờ nó (giới hạn ở 60 giây), trước đây create() trở lại. Nếu không thành công, vùng chứa vừa tạo sẽ được thu thập và lỗi xuất hiện.

Cũng chịu tải: thư mục chứng chỉ của proxy là chmod 0o777 phía máy chủ nên không phải root mitmdump có thể viết CA được tạo của nó vào confdir được gắn liên kết và start() bị chặn cho đến khi tệp CA tồn tại và cổng nghe đã trả lời (ngân sách của thập niên 20, được thử nghiệm bằng python3 ổ cắm kết nối bên trong container).

Vòng đời và thứ tự phá bỏ

_sandbox_proxy(repo, local):                       # runtime/flow.py, wraps the whole flow body
    gate: sandbox_mode == "docker" and (flag or SANDBOX_GIT_TOKEN)   # else pure no-op, imports nothing
    rules  = github_token_rules(token) + DEFAULT_PROXY_RULES
    proxy  = DockerCredentialProxy(build_credential_map(rules))
    proxy.start()                                  # network create → docker run → wait until ready
    install_executor(SandboxExecutor(DockerBackend(network=…, proxy_env=…, ca_cert_host_path=…)))
    prepare_workspace_or_empty(...) + warm the worker   # CA trusted before the first bash
    yield                                          # ← the agent runs here
    finally:
        reap the WORKER first                      # docker network rm FAILS while it is attached
        proxy.stop()                               # stop container → retry network rm (10×) → rmtree certs

Thứ tự xé bỏ không phải là một sự lựa chọn về phong cách: docker network rm thất bại trong khi bất kỳ container nào vẫn còn được đính kèm và quá trình dọn dẹp điểm cuối của daemon sẽ làm chậm quá trình loại bỏ vùng chứa — do đó vòng lặp thử lại bị giới hạn.

Những gì nó đã làm không bảo vệ chống lại

Đi ra đã ** hợp tác **. Công nhân được chỉ vào proxy theo quy ước (http_proxy vars env danh dự của các thư viện khách hàng hoạt động tốt); nó không bao giờ bị ép buộc thông qua nó. Một model muốn bỏ qua nó gõ:

curl --noproxy '*' https://example.com     # walks straight around the proxy

Vì vậy, proxy bảo vệ chống lại một mô hình đọc môi trường của chính nó, không chống lại một mô hình có nghĩa là hại. Đường dẫn nâng cấp là một mạng chỉ dành cho nội bộ với đầu ra từ chối mặc định — chưa bao giờ được xây dựng.

Nó cũng không bao giờ chạm tới thuộc tính thực sự quan trọng — bí mật không bao giờ chạm tới được model bối cảnh. Điều đó xuất phát từ Settings không bao giờ được tuần tự hóa thành một dấu nhắc, từ SecretStrvà từ nhật ký mang tên không phải giá trị. Tất cả những điều đó vẫn tồn tại sau khi xóa.

Tại sao nó bị xóa

Đối số đầy đủ trong ADR-0016. Danh sách ngắn:

  1. Một trong ba chế độ. modal — phần phụ trợ từ xa, nơi mà sự cách ly quan trọng nhất — được tiêm trực tiếp dù sao thì token. Bất biến "Công nhân không có token" đã sai ở đó, với tài liệu ngoại lệ nói như vậy.
  2. Hai cơ chế, một núm vặn. SANDBOX_GIT_TOKEN hoạt động khác nhau tùy thuộc vào phần phụ trợ người dùng đã không chọn vì lý do bảo mật.
  3. Máy móc lớn và dễ vỡ ở những nơi chịu tải — mạng cho mỗi lần chạy, cuộc đua CA với kiểm tra hồi quy của chính nó, một thứ tự chia nhỏ không được sắp xếp lại, một bản cài đặt thích hợp phải được thực hiện đi ra thông qua proxy và token mồi nhử được cài đặt để xoa dịu gh.
  4. Nó chưa bao giờ là rào cản thoát nước (xem ở trên).
  5. Thứ mà nó bảo vệ là PAT có phạm vi, có thể thu hồi người dùng đã cố tình giao cho agent để nó có thể mở chi nhánh và mở PR. Không phải là thông tin xác thực cơ sở dữ liệu sản xuất.

Cái gì đã thay thế nó

Một cơ chế, cả hai phụ trợ (ADR-0016 §2): khi SANDBOX_GIT_TOKEN được thiết lập, nó được đưa trực tiếp vào môi trường Worker dưới dạng GITHUB_TOKEN - docker qua ít giá trị -e GITHUB_TOKEN (vì vậy giá trị không bao giờ nằm trong argv hiển thị trên máy chủ), phương thức thông qua một modal.Secret - cộng với trình trợ giúp thông tin git (x-access-token:$GITHUB_TOKEN) vậy git push qua HTTPS hoạt động và gh xác thực tắt nó. Chưa đặt → hộp cát không có thông tin xác thực nào cả và phía máy chủ Hand-back vẫn vận chuyển Session Branch.

Quy trình đóng hộp cát hiện CÓ THỂ đọc $GITHUB_TOKEN. Đó là giao dịch trung thực: giao nó cho có phạm vi, có thể hủy bỏ PAT. Câu chuyện bảo mật nhỏ hơn — và đúng như đã viết, ở mọi chế độ.

Mục lục nguồn