From e54fbad5b4ac78f903e64578a88a0915c1731a80 Mon Sep 17 00:00:00 2001 From: seoulpro Date: Tue, 28 Jul 2026 01:17:50 +0900 Subject: [PATCH] docs: document privacy-minimized operations --- CHANGELOG.md | 8 ++++ README.ko.md | 23 +++++++++--- README.md | 23 +++++++++--- SECURITY.md | 5 +++ deploy/nginx-access-log.example.conf | 15 ++++++++ docs/CLI.ko.md | 29 ++++++++++++-- docs/CLI.md | 28 ++++++++++++-- docs/INTEGRATION.ko.md | 55 ++++++++++++++++++++++----- docs/INTEGRATION.md | 56 ++++++++++++++++++++++------ tests/collector.test.mjs | 15 ++++++++ 10 files changed, 217 insertions(+), 40 deletions(-) create mode 100644 deploy/nginx-access-log.example.conf diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eac33a..f5552f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this project are documented here. The format follows ## [Unreleased] +### Changed + +- Added a privacy-minimized Nginx source-log example that omits client IPs, + referrers, and query arguments, and clarified the boundary between source-log + contents and Embertop's emitted-event redaction. +- Made SSH preview examples bind explicitly to loopback and documented direct + terminal, web-tunnel, service-status, journal, and `doctor` checks. + ## [0.3.0] - 2026-07-28 ### Added diff --git a/README.ko.md b/README.ko.md index 2e03df2..5764a43 100644 --- a/README.ko.md +++ b/README.ko.md @@ -118,13 +118,14 @@ SSH 인증을 그대로 씁니다. **서버에서**, 로그 읽기 권한만 가진 일반 사용자로: ```bash -embertop serve --host 127.0.0.1 --site example.com --log /var/log/nginx/access.log +embertop serve --host 127.0.0.1 --site example.com --log /var/log/nginx/embertop-access.log ``` **내 컴퓨터에서** 터널을 엽니다: ```bash -ssh -N -L 4318:127.0.0.1:4318 operator@example.com +ssh -N -o ExitOnForwardFailure=yes \ + -L 127.0.0.1:4318:127.0.0.1:4318 operator@example.com ``` **다른 터미널에서** 불 앞에 앉습니다: @@ -133,6 +134,10 @@ ssh -N -L 4318:127.0.0.1:4318 operator@example.com embertop --endpoint http://127.0.0.1:4318/stream ``` +로컬 주소를 명시하면 SSH 클라이언트 설정과 관계없이 전달 포트가 loopback에만 +열립니다. `ExitOnForwardFailure=yes`는 터널을 만들지 못했을 때 즉시 +종료합니다. + 수집기는 `root`가 필요 없습니다. 로그 파일에 대한 읽기 전용 권한이면 충분합니다. 함께 제공되는 systemd 유닛은 `User=embertop`, `Group=adm`를 전제로 한 Linux 예제이므로, 로그 그룹이 다른 배포판에서는 값을 바꿔야 @@ -191,8 +196,8 @@ Linux에서 검증하므로, 다른 운영체제에 배포하거나 의존성 > [!IMPORTANT] > Embertop에는 자체 로그인 기능이 없습니다. 웹 대시보드는 백오피스를 이미 -> 보호하고 있는 인증 뒤에 두십시오. 방문자를 모두 익명화하더라도 요청 -> 빈도와 트래픽 패턴 자체가 운영 정보입니다. +> 보호하고 있는 인증 뒤에 두십시오. 방문자 식별자를 제거해도 요청 빈도와 +> 트래픽 패턴 자체가 운영 정보입니다. 리버스 프록시 구성, 하위 경로 배포, 기존 메트릭 API 재사용은 [연동 가이드](docs/INTEGRATION.ko.md)를 참고하십시오. @@ -211,8 +216,8 @@ embertop --json >> telemetry.jsonl ## 개인정보 -트래픽 데이터는 운영 정보이므로, 가리는 작업을 표시 단계가 아니라 수집 -단계에서 수행합니다. +트래픽 데이터는 운영 정보입니다. Embertop은 각 이벤트를 내보내기 전에 +민감한 값을 정제합니다. - 클라이언트 IP 주소는 내보내지 않습니다. - 쿼리 문자열은 제거합니다. @@ -225,6 +230,12 @@ embertop --json >> telemetry.jsonl - 업스트림 자격 증명은 서버에만 두며, 웹 프록시는 외부 SSE에서 받은 내용을 다시 한번 정제합니다. +이 과정은 원본 로그를 다시 쓰지 않습니다. `EMBERTOP_INCLUDE_PATHS=false` +(또는 `--hide-paths`)는 내보내는 이벤트의 경로만 바꾸며, Nginx가 이미 +디스크에 기록한 줄에는 영향을 주지 않습니다. Embertop용 접근 로그에도 IP +주소·리퍼러·쿼리 문자열을 남기고 싶지 않다면 [연동 +가이드](docs/INTEGRATION.ko.md)의 최소 수집 형식을 사용하세요. + 공개 배포 전에 [SECURITY.md](SECURITY.md)를 읽어 주십시오. ## 개발 diff --git a/README.md b/README.md index fe40f3f..597c86b 100644 --- a/README.md +++ b/README.md @@ -121,13 +121,14 @@ credentials — SSH is already the authentication. **On the server**, as an unprivileged user with read access to the log: ```bash -embertop serve --host 127.0.0.1 --site example.com --log /var/log/nginx/access.log +embertop serve --host 127.0.0.1 --site example.com --log /var/log/nginx/embertop-access.log ``` **On your machine**, open the tunnel: ```bash -ssh -N -L 4318:127.0.0.1:4318 operator@example.com +ssh -N -o ExitOnForwardFailure=yes \ + -L 127.0.0.1:4318:127.0.0.1:4318 operator@example.com ``` **Then, in another terminal**, sit down in front of the fire: @@ -136,6 +137,10 @@ ssh -N -L 4318:127.0.0.1:4318 operator@example.com embertop --endpoint http://127.0.0.1:4318/stream ``` +The explicit local bind keeps the forwarded port on loopback even if the SSH +client is configured otherwise. `ExitOnForwardFailure=yes` exits immediately +when the tunnel cannot be established. + The collector needs no root. Read-only access to the log file is enough. The bundled systemd unit is a Linux example and assumes `User=embertop`, `Group=adm`; distributions that assign logs to another group need that value @@ -195,8 +200,8 @@ deploying elsewhere or after changing the dependency graph. > [!IMPORTANT] > Embertop has no login of its own. Put the web dashboard behind the > authentication that already protects your backoffice. Request rates and -> traffic patterns are operational information even when every visitor is -> anonymous. +> traffic patterns are operational information even after visitor identifiers +> are removed. See [the integration guide](docs/INTEGRATION.md) for reverse-proxy layouts, subpath deployment, and reusing an existing metrics API. @@ -215,8 +220,8 @@ stream reachability, collector bind safety — without starting the UI. ## Privacy -Traffic data is operational data, so the redaction happens at collection, not -at display: +Traffic data is operational data. Embertop sanitizes each event before it is +emitted: - Client IP addresses are never emitted. - Query strings are dropped. @@ -229,6 +234,12 @@ at display: - Upstream credentials stay server-side; the web proxy re-sanitizes anything it receives from a custom SSE source. +This does not rewrite the source log. `EMBERTOP_INCLUDE_PATHS=false` (or +`--hide-paths`) changes the path in emitted events, not a line Nginx already +wrote to disk. Use the privacy-minimized source log in the +[integration guide](docs/INTEGRATION.md) when you do not want IP addresses, +referrers, or query strings in Embertop's own access log. + Read [SECURITY.md](SECURITY.md) before any public deployment. ## Development diff --git a/SECURITY.md b/SECURITY.md index bf23e79..ae91302 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -30,6 +30,11 @@ Embertop treats telemetry as sensitive operational data. - Terminal mode uses the same redaction path as the collector. - Tokens passed with `--token` may be visible in operating-system process listings. Prefer `EMBERTOP_TOKEN` or `EMBERTOP_COLLECTOR_TOKEN`. +- Redaction applies to the event Embertop emits, not to an existing Nginx + source log. A combined log can retain client IPs, referrers, and query strings + before Embertop reads it. `EMBERTOP_INCLUDE_PATHS=false` and `--hide-paths` + change emitted data only. Prefer a privacy-minimized source log, restrict + access to it, and keep retention short. Embertop does not implement user authentication. Deploy it behind the authentication and authorization already protecting your backoffice. A public diff --git a/deploy/nginx-access-log.example.conf b/deploy/nginx-access-log.example.conf new file mode 100644 index 0000000..10109f2 --- /dev/null +++ b/deploy/nginx-access-log.example.conf @@ -0,0 +1,15 @@ +# Privacy-minimized Nginx source log for Embertop. +# +# `log_format` belongs in the Nginx `http` context. `access_log` is valid in +# `http`, `server`, or `location`; move it to the server or location you want +# Embertop to observe when a global log is not appropriate. +# +# This format omits the client IP, referrer, and query arguments. The normalized +# path and User-Agent remain operational data: restrict access to this file and +# configure an appropriate rotation and retention policy. + +log_format embertop '- - - [$time_local] ' + '"$request_method $uri $server_protocol" $status $body_bytes_sent ' + '"-" "$http_user_agent" $request_time'; + +access_log /var/log/nginx/embertop-access.log embertop; diff --git a/docs/CLI.ko.md b/docs/CLI.ko.md index 2a81d4d..ecc279b 100644 --- a/docs/CLI.ko.md +++ b/docs/CLI.ko.md @@ -106,13 +106,14 @@ EMBERTOP_COLLECTOR_TOKEN=secret \ embertop serve \ --host 127.0.0.1 \ --site example.com \ - --log /var/log/nginx/access.log + --log /var/log/nginx/embertop-access.log ``` 내 컴퓨터의 터미널 하나에서: ```bash -ssh -N -L 4318:127.0.0.1:4318 operator@example.com +ssh -N -o ExitOnForwardFailure=yes \ + -L 127.0.0.1:4318:127.0.0.1:4318 operator@example.com ``` 다른 터미널에서: @@ -121,6 +122,28 @@ ssh -N -L 4318:127.0.0.1:4318 operator@example.com embertop --endpoint http://127.0.0.1:4318/stream ``` +로컬 주소를 명시하면 SSH 클라이언트 설정과 관계없이 전달 포트가 loopback에만 +열립니다. `ExitOnForwardFailure=yes`는 터널을 만들지 못했을 때 즉시 +종료합니다. + +서버에 Embertop과 수집기가 이미 실행 중이라면 터미널 프리뷰를 바로 열 수도 +있습니다. + +```bash +ssh -t operator@example.com \ + 'EMBERTOP_LANG=ko embertop --endpoint http://127.0.0.1:4318/stream' +``` + +loopback에만 열린 웹 대시보드는 포트를 전달해서 확인합니다. + +```bash +ssh -N -o ExitOnForwardFailure=yes \ + -L 127.0.0.1:13000:127.0.0.1:3000 operator@example.com +``` + +브라우저에서 `http://127.0.0.1:13000`을 여세요. 터널은 `Ctrl+C`로 +종료합니다. + 수집기에는 `root` 권한이 필요하지 않습니다. 계정에 로그 파일 읽기 전용 권한만 주면 되며, 함께 제공되는 `collector/embertop.service.example`도 `User=embertop`, `Group=adm`를 전제로 합니다. @@ -139,7 +162,7 @@ embertop --endpoint http://127.0.0.1:4318/stream UI를 시작하지 않고 현재 설정을 확인합니다. ```bash -embertop doctor --log /var/log/nginx/access.log +embertop doctor --log /var/log/nginx/embertop-access.log EMBERTOP_TOKEN=secret embertop doctor -e https://host.example/stream ``` diff --git a/docs/CLI.md b/docs/CLI.md index 87f6099..71b06d3 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -107,13 +107,14 @@ On the server: embertop serve \ --host 127.0.0.1 \ --site example.com \ - --log /var/log/nginx/access.log + --log /var/log/nginx/embertop-access.log ``` On your machine, in one terminal: ```bash -ssh -N -L 4318:127.0.0.1:4318 operator@example.com +ssh -N -o ExitOnForwardFailure=yes \ + -L 127.0.0.1:4318:127.0.0.1:4318 operator@example.com ``` And in another: @@ -122,6 +123,27 @@ And in another: embertop --endpoint http://127.0.0.1:4318/stream ``` +The explicit local bind keeps the forwarded port on loopback even if the SSH +client is configured otherwise. `ExitOnForwardFailure=yes` exits immediately +when the tunnel cannot be established. + +For a quick terminal preview when Embertop and the collector are already +running on the server: + +```bash +ssh -t operator@example.com \ + 'embertop --endpoint http://127.0.0.1:4318/stream' +``` + +To preview a loopback-only web dashboard, forward its port: + +```bash +ssh -N -o ExitOnForwardFailure=yes \ + -L 127.0.0.1:13000:127.0.0.1:3000 operator@example.com +``` + +Then open `http://127.0.0.1:13000`. Stop either tunnel with `Ctrl+C`. + The collector does not need `root`. Give its account read-only access to the log file — the bundled `collector/embertop.service.example` assumes `User=embertop`, `Group=adm`. @@ -140,7 +162,7 @@ Additional options: Validate the current setup without starting the UI: ```bash -embertop doctor --log /var/log/nginx/access.log +embertop doctor --log /var/log/nginx/embertop-access.log EMBERTOP_TOKEN=secret embertop doctor -e https://host.example/stream ``` diff --git a/docs/INTEGRATION.ko.md b/docs/INTEGRATION.ko.md index 729143c..c100b2b 100644 --- a/docs/INTEGRATION.ko.md +++ b/docs/INTEGRATION.ko.md @@ -39,17 +39,31 @@ UI는 기존 백오피스 인증과 접근 제어 뒤에 둡니다. 수집기는 ## 접근 로그 연결 -Nginx combined 형식은 그대로 사용할 수 있습니다. +Embertop은 Nginx와 구조화된 JSON 접근 로그를 읽습니다. Nginx에서는 Embertop +전용 원본 로그의 수집 항목을 최소화하는 다음 형식을 권장합니다. ```nginx -log_format embertop '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" $request_time'; +log_format embertop '- - - [$time_local] ' + '"$request_method $uri $server_protocol" $status $body_bytes_sent ' + '"-" "$http_user_agent" $request_time'; + +access_log /var/log/nginx/embertop-access.log embertop; ``` -수집기는 IP가 포함된 원문을 읽지만, 파싱 결과에는 IP 필드가 존재하지 -않습니다. 쿼리 문자열도 즉시 제거됩니다. 로그 파일 읽기 권한만 부여하고 쓰기 -권한은 부여하지 마세요. +이 전용 로그에는 클라이언트 IP·리퍼러·쿼리 문자열이 기록되지 않습니다. +`$uri`는 쿼리 인자를 제외한 Nginx의 정규화된 현재 경로입니다. 경로와 +User-Agent는 원본 로그에 남습니다. Embertop은 User-Agent 원문을 사람·크롤러· +미상이라는 상위 분류로 줄여서 사용합니다. + +기존 combined 로그도 읽을 수 있지만, Embertop이 읽기 전에 클라이언트 IP, +리퍼러, 쿼리 문자열이 포함된 전체 요청 줄을 디스크에 남길 수 있습니다. +Embertop은 내보내는 이벤트에서 해당 값을 제거하거나 가리며, 이미 기록된 원본 +로그를 다시 쓰지는 않습니다. + +수집기에는 로그 읽기 전용 권한만 부여하세요. 다른 계정의 읽기 권한도 제한하고 +운영에 필요한 기간보다 오래 보관하지 마세요. 수집기에 쓰기 권한은 필요하지 +않습니다. 바로 조정해서 쓸 수 있는 예시는 +`deploy/nginx-access-log.example.conf`에 있습니다. ## 리버스 프록시 @@ -70,6 +84,25 @@ location /internal/embertop-stream { 환경 변수를 Nginx 설정에 직접 보간하는 방식은 배포 환경마다 다릅니다. 실제 토큰은 저장소에 커밋하지 마세요. +## 배포 상태 점검 + +다음 명령은 흔히 쓰는 유닛 이름을 예로 듭니다. 실제로 설치한 이름이 다르면 +바꾸세요. + +```bash +sudo systemctl status embertop-collector embertop-web +sudo journalctl -f -u embertop-collector -u embertop-web +``` + +UI를 띄우지 않고 로그 접근과 파싱을 확인합니다. + +```bash +embertop doctor --log /var/log/nginx/embertop-access.log +``` + +`doctor`는 Node.js 버전, 로그 읽기 가능 여부, 로컬 지표 또는 원격 스트림 연결, +수집기 바인딩 안전성을 보고합니다. + ## 직접 SSE 제공 기존 백오피스가 이미 이벤트 스트림을 만들 수 있다면 수집기를 생략할 수 @@ -81,6 +114,8 @@ location /internal/embertop-stream { 1. UI 경로에 기존 백오피스 인증이 적용되는지 확인합니다. 2. 브라우저 개발자 도구에 수집기 토큰이 나타나지 않는지 확인합니다. -3. 샘플 로그에 쿼리 문자열, 숫자 ID, UUID가 남지 않는지 확인합니다. -4. `/api/stream`이 CDN에서 캐시되지 않는지 확인합니다. -5. 연결이 끊겼을 때 UI가 `RECONNECTING`으로 바뀌는지 확인합니다. +3. 원본 로그의 필드·권한·순환·보관 기간이 의도한 설정인지 확인합니다. +4. 쿼리 문자열, 숫자 ID, UUID, 긴 토큰이 포함된 경로가 이벤트에서 정제되는지 + 확인합니다. +5. `/api/stream`이 CDN에서 캐시되지 않는지 확인합니다. +6. 연결이 끊겼을 때 UI가 `RECONNECTING`으로 바뀌는지 확인합니다. diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index b02c9c2..609b011 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -47,20 +47,31 @@ Missing values use the local operating-system measurement. ## Connect access logs -Nginx combined logs work without changes. Appending request time in seconds -adds latency information: +Embertop reads Nginx and structured JSON access logs. The recommended Nginx +format minimizes what Embertop's source log records: ```nginx -log_format embertop '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" $request_time'; +log_format embertop '- - - [$time_local] ' + '"$request_method $uri $server_protocol" $status $body_bytes_sent ' + '"-" "$http_user_agent" $request_time'; + +access_log /var/log/nginx/embertop-access.log embertop; ``` -The collector reads the source line, but its event schema has no IP field. It -also removes the query string and redacts common identifier-shaped path -segments immediately. +This dedicated log does not write a client IP, referrer, or query string. +`$uri` is Nginx's normalized current path without query arguments. The path and +User-Agent still remain in the source log; Embertop uses the latter to reduce a +request to a high-level human, crawler, or unknown classification. + +Ordinary combined logs remain supported, but they can retain client IPs, +referrers, and full request lines with query strings on disk before Embertop +reads them. Embertop removes or masks those fields in the event it emits; it +does not rewrite an existing source log. -Give the collector read-only access to logs. Do not grant write permission. +Give the collector read-only access to the log, restrict who else can read it, +and keep retention no longer than your operating needs. Do not grant the +collector write permission. A ready-to-adapt snippet is included at +`deploy/nginx-access-log.example.conf`. ## Reverse-proxy SSE @@ -81,6 +92,25 @@ location /internal/embertop-stream { How environment variables are inserted into Nginx configuration depends on your deployment system. Never commit the real token. +## Check a running deployment + +The following commands use common unit names; substitute the names used by your +installation: + +```bash +sudo systemctl status embertop-collector embertop-web +sudo journalctl -f -u embertop-collector -u embertop-web +``` + +Validate log access and parsing without starting the UI: + +```bash +embertop doctor --log /var/log/nginx/embertop-access.log +``` + +`doctor` reports the Node.js version, log readability, local metrics or remote +stream reachability, and collector bind safety. + ## Provide SSE directly You may skip the bundled collector if your backoffice already emits SSE. Each @@ -124,6 +154,8 @@ browser. 1. Confirm that backoffice authentication covers the web route. 2. Confirm that collector tokens never appear in browser developer tools. -3. Test sample paths containing queries, numeric IDs, UUIDs, and long tokens. -4. Confirm that `/api/stream` is not cached by a CDN. -5. Confirm that web and terminal clients reconnect after an interruption. +3. Confirm that the source log has the intended fields, permissions, rotation, + and retention. +4. Test emitted paths containing queries, numeric IDs, UUIDs, and long tokens. +5. Confirm that `/api/stream` is not cached by a CDN. +6. Confirm that web and terminal clients reconnect after an interruption. diff --git a/tests/collector.test.mjs b/tests/collector.test.mjs index fe83621..bfd34a2 100644 --- a/tests/collector.test.mjs +++ b/tests/collector.test.mjs @@ -84,6 +84,21 @@ test("parses an Nginx combined log without retaining the visitor IP", () => { assert.equal(JSON.stringify(parsed).includes("203.0.113.42"), false); }); +test("parses the privacy-minimized Nginx log shape", () => { + const parsed = parseNginxLine( + '- - - [26/Jul/2026:12:04:03 +0900] "GET /notes/123456 HTTP/1.1" 200 482 "-" "Mozilla/5.0 Chrome/126 Safari/537.36" 0.042', + ); + + assert.ok(parsed); + assert.equal(parsed.method, "GET"); + assert.equal(parsed.path, "/notes/:id"); + assert.equal(parsed.status, 200); + assert.equal(parsed.durationMs, 42); + assert.equal(parsed.kind, "human"); + assert.equal(parsed.at, "2026-07-26T03:04:03.000Z"); + assert.equal("ip" in parsed, false); +}); + test("parses structured JSON access logs with nested HTTP fields", () => { const parsed = parseJsonLine( JSON.stringify({