Discourse 컨테이너가 unattended-upgrades를 사용합니까?

Discourse docker 컨테이너는 Debian 기반 docker 컨테이너의 OS 패키지를 최신 상태로 유지하기 위해 unattended-upgrades를 사용합니까?

INSTALL-cloud.md 가이드에서 docker 호스트 머신에 unattended-upgrades를 설치할 것을 권장하는 것을 보고, docker 컨테이너 _내부_의 OS 패키지 상태가 궁금해졌습니다. GitHub의 discourse와 discourse_docker 저장소를 모두 검색해 보았지만, unattended-upgrades에 대한 유일한 언급은 해당 설치 문서뿐이었습니다.

그렇다면 Discourse docker 컨테이너는 어떨까요? 그 컨테이너도 unattended-upgrades를 사용합니까?

추가 조사를 해본 결과, unattended-upgrades가 Discourse 도커 컨테이너에 설치되어 있지만 실행되지 않고 있음을 알 수 있었습니다.

우선, 명확하게 설치되어 있는 것을 확인할 수 있습니다.

root@osestaging1-discourse-ose:/var/www/discourse# dpkg -l | grep -i unatt
ii  unattended-upgrades             1.11.2                       all          automatic installation of security upgrades
root@osestaging1-discourse-ose:/var/www/discourse# 

관련 Debian 위키 문서에 따라 unattended-upgrades 패키지의 구성을 더 자세히 살펴보면

기본 구성은 합리적인 것으로 보입니다.

root@osestaging1-discourse-ose:/var/www/discourse# grep -ir 'origin=' /etc/apt/apt.conf.d/50unattended-upgrades 
//      "origin=Debian,codename=${distro_codename}-updates";
//      "origin=Debian,codename=${distro_codename}-proposed-updates";
		"origin=Debian,codename=${distro_codename},label=Debian";
		"origin=Debian,codename=${distro_codename},label=Debian-Security";
root@osestaging1-discourse-ose:/var/www/discourse# cat /etc/apt/apt.conf.d/20auto-upgrades 
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
root@osestaging1-discourse-ose:/var/www/discourse# 

그러나 로그를 확인해 보면 마지막 항목이 1개월 전 것이었습니다.

root@osestaging1-discourse-ose:/var/www/discourse# tail -f /var/log/unattended-upgrades/unattended-upgrades*.log
==> /var/log/unattended-upgrades/unattended-upgrades-dpkg.log <==
Log started: 2019-11-17  12:34:54
(Reading database ... 44559 files and directories currently installed.)
Removing freetype2-doc (2.9.1-3+deb10u1) ...
Log ended: 2019-11-17  12:34:54

Log started: 2019-11-17  12:34:56
(Reading database ... 44389 files and directories currently installed.)
Removing libjs-jquery (3.3.1~dfsg-3) ...
Log ended: 2019-11-17  12:34:57


==> /var/log/unattended-upgrades/unattended-upgrades.log <==
2019-11-26 16:37:47,549 INFO Initial blacklist : 
2019-11-26 16:37:47,550 INFO Initial whitelist: 
2019-11-26 16:37:47,551 INFO Starting unattended upgrades script
2019-11-26 16:37:47,552 INFO Allowed origins are: origin=Debian,codename=buster,label=Debian, origin=Debian,codename=buster,label=Debian-Security
2019-11-26 16:37:50,811 INFO Checking if system is running on battery is skipped. Please install powermgmt-base package to check power status and skip installing updates when the system is running on battery.
2019-11-26 16:37:50,814 INFO Initial blacklist : 
2019-11-26 16:37:50,815 INFO Initial whitelist: 
2019-11-26 16:37:50,815 INFO Starting unattended upgrades script
2019-11-26 16:37:50,815 INFO Allowed origins are: origin=Debian,codename=buster,label=Debian, origin=Debian,codename=buster,label=Debian-Security
2019-11-26 16:37:53,119 INFO No packages found that can be upgraded unattended and no pending auto-removals
^C
root@osestaging1-discourse-ose:/var/www/discourse# 

…그런데 unattended-upgrades에 대해 정의된 기본 systemd 타이머는 하루에 최소 한 번 실행되도록 설정되어 있습니다.

root@osestaging1-discourse-ose:/var/www/discourse# cat /lib/systemd/system/apt-daily.timer
[Unit]
Description=Daily apt download activities

[Timer]
OnCalendar=*-*-* 6,18:00
RandomizedDelaySec=12h
Persistent=true

[Install]
WantedBy=timers.target
root@osestaging1-discourse-ose:/var/www/discourse# cat /etc/systemd/system/apt-daily.timer.d/override.conf
cat: /etc/systemd/system/apt-daily.timer.d/override.conf: No such file or directory
root@osestaging1-discourse-ose:/var/www/discourse# cat /lib/systemd/system/apt-daily-upgrade.timer
[Unit]
Description=Daily apt upgrade and clean activities
After=apt-daily.timer

[Timer]
OnCalendar=*-*-* 6:00
RandomizedDelaySec=60m
Persistent=true

[Install]
WantedBy=timers.target
root@osestaging1-discourse-ose:/var/www/discourse# cat /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf
cat: /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf: No such file or directory
root@osestaging1-discourse-ose:/var/www/discourse# 

하지만, 실제로는 해당 타이머들이 비활성화되어 있습니다.

root@osestaging1-discourse-ose:/var/www/discourse# sudo systemctl status apt-daily.timer
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
root@osestaging1-discourse-ose:/var/www/discourse# sudo systemctl status apt-daily-upgrade.timer
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
root@osestaging1-discourse-ose:/var/www/discourse# 

이것은 unattended-upgrades를 수동으로 실행하여 추가로 확인되었습니다. 이 실행을 통해 두 개의 git 관련 패키지에 대한 업데이트가 필요했던 것으로 나타났습니다.

root@osestaging1-discourse-ose:/var/www/discourse# sudo unattended-upgrade -d
...
Checking: git ([<Origin component:'main' archive:'stable' origin:'Debian' label:'Debian-Security' site:'security.debian.org' isTrusted:True>])
Checking: git-man ([<Origin component:'main' archive:'stable' origin:'Debian' label:'Debian-Security' site:'security.debian.org' isTrusted:True>])
pkgs that look like they should be upgraded: git   
git-man
...
All upgrades installed
InstCount=0 DelCount=0 BrokenCount=0
Extracting content from /var/log/unattended-upgrades/unattended-upgrades-dpkg.log since 2019-12-24 17:32:55
root@osestaging1-discourse-ose:/var/www/discourse# 

위 unattended-upgrades 실행 중에 업데이트된 git 버전은 git (1:2.20.1-2+deb10u1)이었습니다. 이 테스트를 오늘(2019-12-24) 수행했지만, 해당 보안 업데이트는 이미 두 주 전(2019-12-10부터)에 Debian Buster(Discourse 도커 이미지가 구축된 운영체제)에서 사용 가능했습니다!

실제로 이것은 여러 취약점을 수정하는 상당히 심각한 업데이트이며, 여기에는 원격 코드 실행(RCE) 벡터 2개가 포함됩니다. 자세한 정보는 Debian 보안 공지 4581-1에서 확인할 수 있습니다.

그러나 git는 제가 우연히 발견한 예시에 불과합니다. Discourse 도커 컨테이너가 (기본적으로) 실제로 운영체제에 보안 관련 패치를 적용하지 않는다면 이는 매우 우려스러운 상황입니다.

이것은 버그인가요? 아니면 Discourse 팀의 의도적인 결정이었나요? 아니면 Discourse 도커 컨테이너에서 unattended-upgrades를 활성화하는 기능 요청이 있는 경우를 제외하고는 이것이 그냥 기본 설정인가요?

내 추측으로는 의도적으로 꺼져 있는 것 같고, 무언가를 깨뜨릴 수 있기 때문일 것입니다. 그리고 이미지 자체는 심각한 보안 문제가 발견될 때 업데이트됩니다.

원하시면 활성화할 수 있으며, 컨테이너에 진입하기만 하면 됩니다.

실제로 Discourse의 Docker 이미지가 runit을 사용하도록 설정되어 있다는 걸 방금 깨달았습니다. Discourse Docker 컨테이너에 systemd가 설치되어 있을까요?

systemd가 설치되어 있지 않은 것이 unattended-upgrades를 깨뜨리는 원인이라고 추측합니다.

맞는 말이네요. systemd를 정말로 싫어하는 사람들이 꽤 있습니다. 따라서, 이 시스템을 의존하는 대규모 전문 팀이 정기적으로 기본 컨테이너를 업데이트해 준다는 것을 믿거나, 아니면 다른 방식으로 직접 처리하면서 아무것도 깨지지 않기를 바랄 수 있습니다.

아니요, 사용하지 않으며 그렇게 할 계획도 없습니다. 호스트 OS를 최신 보안 패치가 적용된 상태로 유지하는 것이 권장됩니다.

참고로 실제로 그렇게 되고 있습니다.

참고로, unattended-upgrades 설치 문제(실제로 Discourse Docker 컨테이너에서는 systemd 없이 실행되지 않음)를 크론 작업(cron job)을 통해 실행하도록 트리거를 설정하여 수정했습니다.

필요한 크론 작업을 생성하기 위해 /var/discourse/templates/ 디렉터리에 다음 YAML 템플릿 파일을 생성했습니다(Debian 기반 Discourse Docker 이미지에 포함된 크론 버그를 수정하는 명령도 포함되어 있음에 유의):

cat << EOF > /var/discourse/templates/unattended-upgrades.template.yml
run:
  - file:
     path: /etc/cron.d/unattended-upgrades
     contents: |+
        ################################################################################
        # File:    /etc/cron.d/unattended-upgrades
        # Version: 0.2
        # Purpose: run unattended-upgrades in lieu of systemd. For more info see
        #           * https://wiki.opensourceecology.org/wiki/Discourse
        #           * https://meta.discourse.org/t/does-discourse-container-use-unattended-upgrades/136296/3
        # Author:  Michael Altfield <michael@opensourceecology.org>
        # Created: 2020-03-23
        # Updated: 2020-04-23
        ################################################################################
        20 04 * * * root /usr/bin/nice /usr/bin/unattended-upgrades --debug
        

  - exec: /bin/echo -e "\n" >> /etc/cron.d/unattended-upgrades
  # fix the Docker cron bug https://stackoverflow.com/questions/43323754/cannot-make-remove-an-entry-for-the-specified-session-cron
  - exec: /bin/sed --in-place=.\`date "+%Y%m%d_%H%M%S"\` 's%^\([^#]*\)\(session\s\+required\s\+pam_loginuid\.so\)$%\1#\2%' /etc/pam.d/cron
EOF

위 템플릿 파일을 활성화하려면 애플리케이션의 YAML 파일에 있는 templates 목록에 추가해야 합니다. 예를 들어,

[root@osestaging1 discourse]# head -n20 /var/discourse/containers/app.yml
## this is the all-in-one, standalone Discourse Docker container template
##
## After making changes to this file, you MUST rebuild
## /var/discourse/launcher rebuild app
##
## BE *VERY* CAREFUL WHEN EDITING!
## YAML FILES ARE SUPER SUPER SENSITIVE TO MISTAKES IN WHITESPACE OR ALIGNMENT!
## visit http://www.yamllint.com/ to validate this file as needed

templates:
  - "templates/unattended-upgrades.template.yml"
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
#  - "templates/web.socketed.template.yml"
  - "templates/web.modsecurity.template.yml"
  - "templates/web.ratelimited.template.yml"
## Uncomment these two lines if you wish to add Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"
[root@osestaging1 discourse]# 

새로 설치된 Discourse Docker 이미지(Debian 10 기반)에서 활성화되어 있는 것으로 보입니다. 실행 경로는 runit → cron → /etc/cron.daily/apt-compat → /usr/lib/apt/apt.systemd.daily 입니다.

또한 1년 된 Ubuntu 16 기반 Docker 이미지(discourse/base:2.0.20190321-0122 기반)에서도 실행되고 있었으며, 커스터마이징 없이 그대로 실행된 것으로 확신합니다.

systemd가 설치되어 있지 않아 실제로는 실행되지 않습니다.