서버 보안
Signal Lab 서버는 실행 중인 컴퓨터에서 실제 트래픽을 보냅니다. OSC, UDP, HTTP, MQTT, 스톰, 스캔, 브로드캐스트입니다. 서버를 사용할 수 있는 사람은 그 컴퓨터에서 이 모든 것을 할 수 있으므로, 서버는 기본적으로 닫혀 있으며 토큰이 있을 때만 열립니다.
WARNING
액세스 토큰은 그 컴퓨터 네트워크의 비밀번호처럼 다루십시오. 토큰을 가진 사람은 서버가 닿는 어느 곳으로든 서버에서 트래픽을 보낼 수 있습니다.
한눈에 보기
- 토큰이 없으면 이 컴퓨터에서만. 토큰이 없으면 서버는 루프백에서만 수신하며 루프백 호스트 이름에만 응답합니다. 다른 주소에서는 시작을 거부합니다.
- 그 밖의 사용자에게는 토큰. 브라우저는 한 번 로그인하면 세션 쿠키를 받고, 스크립트는 모든 요청과 함께 토큰을 보냅니다.
- 자체 페이지만. 무언가를 바꾸는 요청과 이벤트 WebSocket은 서버 자체의 오리진에서 와야 하며, 명령은 JSON만 받습니다.
- 자체 이름만. 서버가 응답하지 않는 호스트 이름은 거부되므로 DNS 리바인딩을 막습니다.
- 시크릿은 안에 머뭅니다. 환경 변수나 파일에서 읽기 전용으로만 읽고, 절대 반환하지 않으며, 나타날 만한 곳에서는 모두 가려 둡니다.
- 맡은 일 이상은 하지 않습니다. 호스트의 방화벽을 바꾸지 않고, 데이터 폴더 밖의 파일을 제공하지 않으며, 특별한 권한도 필요하지 않습니다.
토큰 없이: 이 컴퓨터에서만
토큰 없이 시작한 서버는 127.0.0.1:1430에서 수신하며 로그인이 필요 없습니다. 이 컴퓨터 앞에 있는 사람을 위한 도구이기 때문입니다. 이 컴퓨터의 브라우저에서 열린 웹 페이지가 127.0.0.1로 확인되는 이름을 통해 서버에 접근하는 것(DNS 리바인딩)을 막기 위해, 서버는 Host가 루프백 이름 (localhost, .localhost로 끝나는 이름, 127.x.x.x, [::1])이거나 --allowed-host로 허용한 이름인 요청에만 응답합니다.
토큰 없이 다른 주소에서 수신하라는 요청을 받으면 서버는 시작하지 않고, 이유를 알린 뒤 종료 코드 2로 끝납니다.
토큰
토큰은 공백이나 줄바꿈 없이 24자 이상입니다. signal-lab-server token은 16진수 64자의 임의 토큰을 출력하고, --generate-token(이미지에서는 켜져 있음)은 처음 시작할 때 토큰을 만들어 서버 자체 사용자만 읽을 수 있게 데이터 폴더에 보관하고 한 번 출력합니다. 토큰을 주는 모든 방법은 액세스 토큰을 참고하십시오.
토큰 비교는 어디서 달라지든 같은 시간이 걸리며, 틀린 토큰은 1초를 기다리게 하고 로그에 경고를 남깁니다. 추측은 느리고 흔적을 남깁니다.
브라우저: 세션
로그인하지 않은 브라우저는 로그인 페이지로 이동합니다. 토큰은 한 번 세션으로 교환되어 쿠키에 담기며, 이 쿠키는 다음과 같습니다.
HttpOnly— 페이지의 어떤 스크립트도 읽을 수 없습니다.SameSite=Strict— 다른 사이트의 페이지가 브라우저로 하여금 이 쿠키를 보내게 할 수 없습니다.- 7일 동안 유효합니다.
--secure-cookie를 쓰면Secure이므로 HTTPS로만 전송됩니다(HTTPS 프록시 뒤에서 설정하십시오).
세션은 서버의 메모리에 있습니다. 다시 시작하면 모든 사용자가 로그아웃되며, 로그아웃 버튼은 세션 하나를 즉시 끝냅니다. 세션은 최대 1024개를 보관하며, 가장 오래된 것부터 지웁니다.
스크립트: 베어러 토큰
스크립트, signallab --server, CI는 모든 요청과 함께 토큰을 보냅니다.
Authorization: Bearer <token>GET /api/health(서버가 응답하는지, 버전, 토큰을 요구하는지)와 로그인 페이지를 제외한 모든 엔드포인트는 토큰이나 세션이 필요합니다. 토큰 없이 API에 요청하면 auth.required 오류와 함께 401을 받고, 페이지를 요청하면 로그인 페이지가 나옵니다.
호스트 이름
| 서버 시작 방식 | 응답하는 호스트 이름 |
|---|---|
| 토큰 없음 | 루프백 이름과 --allowed-host에 있는 이름 |
토큰 있음, --allowed-host 없음 | 모든 이름 |
토큰 있음, --allowed-host 있음 | 루프백 이름과 --allowed-host에 있는 이름 |
--allowed-host(SIGNALLAB_ALLOWED_HOSTS)는 쉼표로 구분한 이름을 받으며, 포트를 빼고 대소문자를 구분하지 않고 비교합니다.
signal-lab-server --listen 0.0.0.0:1430 --token-file token.txt --allowed-host lab-pc.example.com,192.0.2.10그 밖의 Host는 auth.host 오류와 함께 403을 받습니다. 알려진 이름으로 접근할 수 있는 서버에서 이 옵션을 설정하면, 다른 사이트의 페이지가 자기 이름으로 서버에 접근하지 못합니다.
오리진과 콘텐츠 유형
- 무언가를 바꾸는 모든 요청(
GET과HEAD가 아닌 모든 요청)과 이벤트 WebSocket은Origin이 없거나 서버 자체의 오리진(Host와 같은 호스트와 포트)이어야 합니다. 다른 사이트의 페이지이거나Origin: null을 보내는 페이지는auth.origin오류와 함께403을 받습니다. 스크립트와curl은Origin을 보내지 않으므로 영향을 받지 않습니다. - 명령은
Content-Type: application/json만 받으며(그 밖에는command.json_required오류와 함께415), 다른 사이트의 폼은 명령을 보낼 수 없습니다. - 서버는 교차 출처(CORS) 요청에 응답하지 않습니다.
응답 헤더
모든 응답에는 다음 헤더가 붙습니다.
| 헤더 | 값 |
|---|---|
Content-Security-Policy | default-src 'self'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self' |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | same-origin |
Cache-Control | API와 로그인 페이지에는 no-store |
인터페이스는 서버가 제공하는 것만 불러오고, 서버하고만 통신하며, 다른 페이지가 프레임으로 삽입할 수 없습니다.
파일과 크기
- 다운로드(
GET /api/files?path=…)는 데이터 폴더 안에 있는 파일만 최대 256 MiB까지 제공하며, 그 밖의 경로는404입니다. - 요청 본문은 최대 24 MiB입니다.
시크릿
시크릿 값은 엔진 밖으로 나가지 않습니다.
- 서버에서 값은 읽기 전용입니다. 환경 변수
SIGNALLAB_SECRET_<NAME>또는 시크릿 폴더(기본값/run/secrets/signallab)의<NAME>파일입니다. 브라우저에서 설정하거나 제거하려는 시도는 거부되므로(secret.read_only), 페이지에 입력한 값이 더 약한 곳에 저장되는 일이 없습니다. 시크릿을 참고하십시오. - 값을 반환하는 명령은 없습니다. 인터페이스는 이름이 설정되어 있는지만 알 수 있습니다.
- 실험은 시크릿을
{{secret.NAME}}으로 지정합니다. 실행이나 전송이 시크릿을 사용하는 동안 보고하는 모든 텍스트(단계, 오류, 실행 보고서)에는 그 자리에••••표시가 나오며, 인스펙터의 프레임은 바이트 단위로 가려집니다. - HTTP 노드의 자격 증명은 요청을 보내는 순간에만
Authorization헤더가 됩니다. 단계, 프레임, 보고서에는 응답만 담기고 이 헤더는 절대 담기지 않습니다.
기록되는 것
로그에는 클라이언트의 주소와 함께 시작된 모든 작업(스톰, 스캔, 브로드캐스트, 모니터, 발생기, 실행, 에뮬레이터), 모든 로그인, 틀린 토큰으로 시도한 모든 로그인이 기록됩니다. 로그를 참고하십시오.
서버가 절대 하지 않는 일
- 호스트의 방화벽 변경. 데스크톱 앱은 사용자가 요청하면 방화벽 규칙을 추가할 수 있지만, 서버에서는 그 명령이 거부됩니다(
firewall.server). 호스트의 방화벽은 호스트를 운영하는 사람의 몫입니다. (명령 하나로 설치하는 스크립트는 ufw나 firewalld에서 서버의 포트를 열 것인지 제안하며, 먼저 묻습니다. 호스트의 방화벽을 참고하십시오.) - 루프백이 아닌 주소에서 토큰 없이 접근 가능하게 시작.
- 데이터 폴더 밖의 파일 제공.
- 브라우저에 입력한 시크릿 저장.
- TLS 직접 처리. 앞에 HTTPS 프록시를 두십시오(HTTPS 프록시 뒤에서 참고).
엔진의 모든 한도, 즉 스윕은 최대 1024개 호스트, 비컨은 초당 최대 50,000개 패킷이라는 한도는 서버에서도 앱과 똑같이 적용됩니다. 이 한도는 안전장치일 뿐 허가가 아닙니다. 직접 소유했거나 테스트할 수 있는 시스템으로만 트래픽을 보내십시오.
컨테이너
이미지는 권한 없는 사용자(uid와 gid 10001)로 실행되며 /data에만 씁니다. 설치 스크립트와 deploy/compose.yaml이 시작하는 대로, 읽기 전용 루트 파일 시스템, capability 없음, no-new-privileges 설정으로도 변경 없이 실행됩니다. 각 이미지는 SBOM, 빌드 출처 증명, 서명된 GitHub 증명과 함께 게시됩니다.
gh attestation verify oci://ghcr.io/proanima/signallab:1.0.0 -R ProAnima/SignalLabSignal Lab이 외부로 보내는 것
사용자가 보내는 트래픽 외에 Signal Lab은 스튜디오가 운영하는 두 곳과 통신합니다.
업데이트 확인
업데이트를 찾는 것은 데스크톱 앱뿐이며, 서버와 브라우저는 절대 찾지 않습니다. (Signal Lab 정보에 있는) 하루에 한 번 확인 옵션이 켜져 있으면 하루에 한 번, 그리고 업데이트 확인 버튼을 누를 때마다 앱은 스튜디오의 허브(hub.proanima.net)에 묻고, 허브에 연결할 수 없을 때만 GitHub의 최신 릴리스에 묻습니다. 요청에는 다음이 담깁니다.
- 앱의 버전.
- 운영 체제와 프로세서 아키텍처.
- 이 설치의 임의 번호(
X-Install-Id). 한 번 만들어 앱의 설정과 함께 보관하며, 새 릴리스를 일부 설치에 먼저 배포할 수 있게 합니다. 사용자나 컴퓨터에 대한 정보는 담겨 있지 않습니다.
게시된 릴리스만 제공됩니다. 서명이 앱에 내장된 키와 일치하지 않는 다운로드는 설치되지 않으며, 설치 후 다시 시작 버튼을 누르기 전에는 아무것도 설치되지 않습니다.
피드백
개발자에게 문의 기능(헤더의 ✉ 버튼이며, Signal Lab 정보에도 있습니다)은 스튜디오의 허브를 거쳐 개발자에게 메시지를 보내고, 허브가 이를 메일로 전달합니다. 앱에는 이를 위한 비밀번호가 없습니다. 양식에 표시되는 것만 보냅니다. 입력한 메시지, 입력했다면 이메일, 첨부한 스크린샷, 그리고 자동 첨부 항목 아래의 콘솔 로그와 버전 및 시스템 정보이며, 이 둘은 보내기 전에 열어 보고 선택을 해제할 수 있습니다. 여기서 이 컴퓨터의 이름, 주소, 사용자의 폴더는 빠집니다. 브라우저에서는 서버가 양식을 보냅니다.