O container do Discourse usa o unattended-upgrades?

O contêiner Docker do Discourse utiliza unattended-upgrades para manter os pacotes do sistema operacional do contêiner Docker baseado em Debian atualizados?

Notei que o guia INSTALL-cloud.md recomenda instalar o unattended-upgrades na máquina hospedeira do Docker, o que me fez questionar o estado dos pacotes do sistema operacional dentro do contêiner Docker. Pesquisei tanto nos repositórios discourse quanto discourse_docker no GitHub, mas a única referência que encontrei ao unattended-upgrades foi o mesmo documento de instalação:

Mas e quanto ao contêiner Docker do Discourse? Será que ele utiliza unattended-upgrades?

Após mais investigações, parece que o unattended-upgrades está instalado, mas não em execução no contêiner Docker do Discourse.

Primeiro, está claramente instalado:

root@osestaging1-discourse-ose:/var/www/discourse# dpkg -l | grep -i unatt
ii  unattended-upgrades             1.11.2                       all          instalação automática de atualizações de segurança
root@osestaging1-discourse-ose:/var/www/discourse# 

Uma inspeção mais detalhada da configuração do pacote unattended-upgrades, conforme o artigo relevante da wiki do Debian, mostra:

A configuração padrão parece correta:

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# 

No entanto, a verificação dos registros mostra que a última entrada foi há 1 mês:

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# 

…Mesmo que os temporizadores systemd padrão definidos para o unattended-upgrades estejam configurados para executar pelo menos uma vez por dia:

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# 

Mas, de fato, esses temporizadores estão desativados.

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# 

Isso é confirmado ainda mais ao executar manualmente o unattended-upgrades, que acabou por exigir uma atualização para dois pacotes relacionados ao 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# 

A versão do git que foi atualizada na execução acima do unattended-upgrades foi git (1:2.20.1-2+deb10u1). Executei este teste hoje (2019-12-24), mas a atualização de segurança estava disponível para o Debian Buster (o sistema operacional no qual a imagem Docker do Discourse é construída) há duas semanas (desde 2019-12-10)!

Esta é, na verdade, uma atualização bastante séria que corrige várias vulnerabilidades, incluindo dois vetores para Execução Remota de Código. Mais informações estão disponíveis no Advisory de Segurança do Debian 4581-1:

Mas o git é apenas um exemplo em que acabei de tropeçar. É extremamente preocupante se o contêiner Docker do Discourse não aplica (por padrão) realmente patches relacionados à segurança ao seu sistema operacional.

Isso é um bug? Ou foi uma decisão intencional da equipe do Discourse? Ou isso é apenas o padrão no lugar de uma solicitação de recurso para habilitar o unattended-upgrades no contêiner Docker do Discourse?

Minha suposição é que esteja desativado de propósito, pois isso poderia quebrar algo, e a própria imagem é atualizada quando um problema de segurança grave é encontrado.

Você pode ativá-lo se quiser, basta entrar no contêiner.

Na verdade, acabei de perceber que a imagem Docker do Discourse está configurada para usar runit. O systemd está mesmo instalado no container Docker do Discourse?

Minha suposição é que a ausência do systemd instalado é o que está quebrando o unattended-upgrades.

Parece correto. Algumas pessoas realmente não gostam do systemd. Então, você pode confiar no fato de que o container base é atualizado regularmente por uma equipe grande de profissionais que depende dele, ou pode fazer isso de outra forma, por conta própria, e torcer para não quebrar nada.

Não, ele não usa e não pretendemos fazer com que use. A recomendação é manter seu sistema operacional host atualizado com os últimos patches de segurança.

O que, para registro, é verdade.

FYI, corrigi a instalação quebrada do unattended-upgrades (que na verdade não executa sem systemd no contêiner Docker do Discourse) ao acioná-lo para rodar via um job de cron.

Criei o seguinte arquivo de modelo YAML no meu diretório /var/discourse/templates/ para criar o job de cron necessário (observe que ele também inclui um comando para corrigir um bug com cron presente na imagem Docker do Discourse baseada em Debian):

cat << EOF > /var/discourse/templates/unattended-upgrades.template.yml
run:
  - file:
     path: /etc/cron.d/unattended-upgrades
     contents: |+
        ################################################################################
        # Arquivo:    /etc/cron.d/unattended-upgrades
        # Versão: 0.2
        # Propósito: executar unattended-upgrades no lugar do systemd. Para mais informações, veja
        #           * https://wiki.opensourceecology.org/wiki/Discourse
        #           * https://meta.discourse.org/t/does-discourse-container-use-unattended-upgrades/136296/3
        # Autor:  Michael Altfield <michael@opensourceecology.org>
        # Criado: 2020-03-23
        # Atualizado: 2020-04-23
        ################################################################################
        20 04 * * * root /usr/bin/nice /usr/bin/unattended-upgrades --debug
        

  - exec: /bin/echo -e "\n" >> /etc/cron.d/unattended-upgrades
  # corrige o bug do cron no Docker 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

Para habilitar o arquivo de modelo acima, você precisa adicioná-lo à sua lista de templates no arquivo YAML do seu aplicativo. Por exemplo:

[root@osestaging1 discourse]# head -n20 /var/discourse/containers/app.yml
## este é o modelo de contêiner Docker Discourse all-in-one, standalone
##
## Após fazer alterações neste arquivo, você DEVE reconstruir
## /var/discourse/launcher rebuild app
##
## TENHA *MUITO* CUIDADO AO EDITAR!
## ARQUIVOS YAML SÃO SUPER SENSÍVEIS A ERROS DE ESPAÇAMENTO OU ALINHAMENTO!
## visite http://www.yamllint.com/ para validar este arquivo conforme necessário

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"
## Descomente estas duas linhas se quiser adicionar o Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"
[root@osestaging1 discourse]# 

Parece que isso está habilitado em uma imagem Docker fresca do Discourse (executando Debian 10). Ela é iniciada através do runit → cron → /etc/cron.daily/apt-compat → /usr/lib/apt/apt.systemd.daily

Também estava rodando em uma imagem Docker baseada no Ubuntu 16 com um ano de idade (baseada em discourse/base:2.0.20190321-0122), tenho certeza de que sem nenhuma personalização.

O systemd não está instalado, então ele nunca é executado de fato.