본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

TCC signed launcher

~11 min · war-story, permissions, code-signing, process-group

Level 0Empty Shelf
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"권한이 네 코드한테 거부된 게 아냐. 네 코드의 identity 한테 거부된 거고 — 벌거벗은 인터프리터엔 그게 거의 없어."

같은 Python, 다른 두 답

두 번째 전쟁 이야기고, 지난번이랑 사람 미치게 하는 모양이 같아. executor 는 마운트된 볼륨에서 영상 아카이브를 읽어야 하는데, 요즘 macOS 에서는 그러려면 유저가 full-disk 파일 접근을 승인해줘야 해. 승인해주고, SSH 로 들어가서 Python 을 돌리면 아카이브를 완벽하게 읽어. 그런데 서비스 매니저가 정확히 같은 인터프리터를 같은 기계에서 띄우면 운영체제가 파일 접근을 거부해.

이유는 권한 시스템이 코드한테 접근을 승인하는 게 아니기 때문이야. identity 한테 승인해. 코드 서명된 애플리케이션, 그러니까 책임을 지는 프로세스한테 귀속되는 identity. 인터랙티브 셸 세션은 시스템이 이미 신뢰하는 identity 를 달고 있고, 네 Python 이 그 컨텍스트를 물려받아. 반면 서비스 매니저가 띄운 맨 인터프리터 바이너리한테는 승인해줄 만한 identity 가 없어. 그래서 요청이 유저가 승인한 적 없는 무언가로 착지하고, 거부되는 거야.

게다가 놓치기 쉬운 방식으로 더 나빠져. 그 Python 바이너리한테 직접 접근을 승인해줘도, 평범한 환경 업데이트가 그 바이너리를 갈아끼울 수 있어. 그럼 그 바이너리에 붙어 있던 승인이 증발하고. 다음에 툴링을 업데이트하는 순간 조용히 만료되는 권한을 만들어둔 셈이지. 갈아끼울 수 있는 파일한테 준 승인은, 네가 고른 적도 없고 통보받지도 못할 만료일이 붙은 승인이야.

고침: 승인이 살 안정된 identity 를 줘

Recall 의 답은 OS 한테 교체 가능한 파일을 믿어달라고 하길 그만두는 거야. 서비스 매니저가 안정된 bundle identifier 를 가진 작고 기계 로컬인 코드 서명된 애플리케이션 번들을 띄워. 시스템이 보는 건 그 launcher 야. 책임을 지는 부모 프로세스고, 런타임 Python 을 자식으로 spawn 하지. full-disk 접근은 안정된 앱 identity 한테 한 번 승인돼. 다음 주에 conda 가 갈아끼울 수 있는 인터프리터가 아니라. 그래서 권한이 밑에 깔린 툴체인의 온갖 일상 업데이트보다 오래 살아남아. 흔들리지 않는 것에 붙어 있으니까.

여기서 가져갈 원리는 이거야. 권한 승인은 identity 에 붙으니까, identity 는 안정적이어야 하고 네가 일부러 소유해야 해. 네 승인이 일상적으로 재생성되거나 업그레이드되거나 재설치되는 것에 고정돼 있으면, 그건 접근을 승인받은 게 아니라 다음 업데이트 때까지 빌린 거야.

나중에 무는 디테일: 신호를 전달해

서비스 매니저와 진짜 작업 사이에 launcher 를 끼워 넣으면 새 의무가 하나 생겨. 그걸 건너뛰면 고약한 부류의 버그가 나오고. 서비스 매니저는 job 을 멈출 때 자기가 아는 프로세스, 그러니까 launcher 한테 신호를 보내. launcher 가 그냥 죽어버리면 걔가 spawn 한 Python 자식이 고아가 될 수 있어. 전사는 아직 돌고 있고, lease 도 아직 쥐고 있는데, 네가 통제한다고 믿는 lifecycle 에는 안 보이는 상태로. 그래서 launcher 는 종료 신호를 worker 프로세스 그룹 전체에 전달해. restart 는 restart 여야지, 'restart 에 아직 일하는 유령 덤' 이면 안 되니까. 진짜 작업 앞에 supervisor 를 세울 때마다, 그 supervisor 의 일은 시작하는 것만이 아냐. 멈춤이 자기가 시작한 전부를 실제로 멈추게 만드는 것까지야.

Code

인터프리터 말고 identity 에 승인 — 그리고 신호를 전달·text
틀림: 서비스 매니저 -> 벌거벗은 인터프리터 바이너리
  - 안정된 승인 가능 identity 없음 -> 파일 접근 거부
  - 승인해도: 환경 업데이트가 바이너리를 교체
    -> 승인이 조용히 증발

맞음: 서비스 매니저 -> signed app bundle (안정된 bundle id)
                            └─ 런타임 Python 을 자식으로 spawn
  - full-disk 접근이 안정된 앱 identity 에 한 번 승인됨
  - 그 밑 conda/툴체인 업데이트를 살아남음
  - launcher 가 책임 부모 프로세스

의무: launcher 는 종료 신호를 전체 worker 프로세스 그룹에
전달해야 함 -- 아니면 '정지' 가 lease 쥔 live 전사를 고아로 만듦.

External links

Exercise

네 설정에서 특정 산물에 붙어 있는 권한이나 자격 증명이나 접근 승인을 찾아봐. 바이너리 경로, 생성된 key 파일, 컨테이너 이미지 태그, 인터프리터 같은 거. 그리고 물어봐. 어떤 일상적인 행위가 그 산물을 갈아끼우고, 그때 승인이 살아남아? 그다음 스택에 있는 supervisor 나 wrapper 를 봐. 멈출 때 자기가 시작한 전부한테 종료를 전달해? 아니면 자식이 유령으로 살아남을 수 있어?
Hint
탐침 두 개. 하나, 네 승인이 고정돼 있는 대상을 갈아끼우는 업데이트를 하나 대봐. 패키지 업그레이드든 재빌드든 재배포든. 승인이 그것과 함께 죽으면, 네가 일부러 소유하는 안정된 identity 에 다시 고정해. 둘, supervisor 를 멈춘 뒤에 자식 프로세스가 아직 살아 있는지 확인해. 작업을 살려두는 정지는 아예 정지가 없는 것보다 나빠. 툴링은 성공했다고 보고하는데 유령이 리소스를 계속 쥐고 있으니까.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.