solve apt update failure in a Ubuntu container

Dec 30, 2022 12:44 · 1063 words · 5 minute read

docker container 内で、apt update すると以下のエラーが発生。

+ sudo apt-get update -qqy
W: https://download.docker.com/linux/ubuntu/dists/jammy/InRelease: Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in apt-key(8) for details.
E: Problem executing scripts APT::Update::Post-Invoke-Success '/usr/bin/test -e /usr/share/dbus-1/system-services/org.freedesktop.PackageKit.service && /usr/bin/test -S /var/run/dbus/system_bus_socket && /usr/bin/gdbus call --system --dest org.freedesktop.PackageKit --object-path /org/freedesktop/PackageKit --timeout 4 --method org.freedesktop.PackageKit.StateHasChanged cache-update > /dev/null; /bin/echo > /dev/null'
E: Sub-process returned an error code
E: Problem executing scripts APT::Update::Post-Invoke 'rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true'
E: Sub-process returned an error code

TL;DR 🔗

  • その1: Docker Engine のバージョンを 20.10.10 以上にあげる。
  • その2: ベースイメージを、GLibc 2.33 以下のものに変更する。
  • その3: docker コマンド実行時に --security-opt seccomp=unconfined をつける。
    • ただしセキュリティリスクが増加するので注意。
    • build コマンドはサポートされていない場合がある。
  • その4: (focal/hirsute のみ) ワークアラウンドPPA をインストールする。
  • その5: (試してない) このPR のセキュリティポリシーを seccopm に渡して実行すればいけるかも…?

再現手順 🔗

このエラーは、CircleCI で docker image を build しようとした際に発生した。
CircleCI はジョブの実行環境として docker container を使用する際に setup_remote_docker を宣言することで独立した docker 実行環境を構成し、DooD を実行可能なように構成を組んでくれる。(推測: ジョブの実行コンテナのホスト環境に docker engine を立ち上げて、ジョブの実行コンテナ内に socket ファイルをマウントしてくれているのではなかろうか。)

エラーが発生したのはサーバー版の CircleCI であり、2022-12-30 現在、サーバー版の CircleCI では remote docker のバージョン指定をサポートしていない。
https://circleci.com/docs/ja/building-docker-images/#docker-version

Docker

Server Engine Details:
  Version:          20.10.7
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       b0f5bc3
  Built:            2021-06-02T11:54:50.000000000+00:00
  OS/Arch:          linux/amd64
  Experimental:     false

ジョブ実行環境のコンテナとしては cimg/go:1.18 を利用しており、cimg/go:1.18 は 1.18.9 のイメージを利用する。

このコンテナ内で sudo apt-get update -qqy を実行すると、前述の error が発生する。

ローカルマシンで実行してみる 🔗

ローカルマシンでは、コンテナランタイムとして colima を使っている。

Server:
 Engine:
  Version:          20.10.11
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.15
  Git commit:       847da184ad5048b27f5bdf9d53d070f731b43180
  Built:            Wed Apr 13 23:41:08 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.5.8
  GitCommit:        1e5ef943eb76627a6d3b6de8cd1ef6537f393a71
 runc:
  Version:          1.0.0-rc95
  GitCommit:        b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
 docker-init:
  Version:          0.19.0
  GitCommit:       

こちらで同じ手順を踏んでも、apt key 関連の warning は出るものの、エラーは再現しなかった。
ホストマシンか、コンテナランタイム、または docker engine のバージョンが怪しそう。

原因の箇所 🔗

apt のソースコードを見に行くと、 AcquireUpdate から、 RunScripts を呼び出しているが、これは単にスクリプトを fork した子プロセスで実行しているだけ。

どうやら本当の問題は、glibc 2.34 以降で使われている clone3 システムコールが原因らしい。
Docker Engine 20.10.9 以前のバージョンでは、デフォルトのポリシーで clone3 システムコールに対応しておらず、Permission Denied として処理される。

これを サポートする対応 が Docker 20.10.10 にバックポートされている。

https://matsuand.github.io/docs.docker.jp.onthefly/engine/release-notes/#201010

ここが参考になった: https://pascalroeleven.nl/2021/09/09/ubuntu-21-10-and-fedora-35-in-docker/

Debian のパッケージ管理周りのおさらい 🔗

apt はリポジトリからパッケージを取得してローカルマシンに展開する。
リポジトリの場所は /etc/apt/sources.listdeb uri distribution <component>... というフォーマットで記述されている。

# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://archive.ubuntu.com/ubuntu/ jammy main restricted
# deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted

## Major bug fix updates produced after the final release of the
## distribution.
deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted

## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team. Also, please note that software in universe WILL NOT receive any
## review or updates from the Ubuntu security team.
deb http://archive.ubuntu.com/ubuntu/ jammy universe
# deb-src http://archive.ubuntu.com/ubuntu/ jammy universe
deb http://archive.ubuntu.com/ubuntu/ jammy-updates universe
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates universe

## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team, and may not be under a free licence. Please satisfy yourself as to
## your rights to use the software. Also, please note that software in
## multiverse WILL NOT receive any review or updates from the Ubuntu
## security team.
deb http://archive.ubuntu.com/ubuntu/ jammy multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy multiverse
deb http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse

## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse

deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted
# deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted
deb http://security.ubuntu.com/ubuntu/ jammy-security universe
# deb-src http://security.ubuntu.com/ubuntu/ jammy-security universe
deb http://security.ubuntu.com/ubuntu/ jammy-security multiverse
# deb-src http://security.ubuntu.com/ubuntu/ jammy-security multiverse

たとえば上記の例の3行目に関しては、http://archive.ubuntu.com/ubuntu/dists/jammy/main/ がパッケージの配布場所となっている。

curl のバイナリ取得を手動でやってみる。 🔗

試しに Ubuntu で curl のバイナリ取得を手動でやってみる。
まずは curl のありかを確認する。

$ apt search curl
circleci@5618423a1566:~/project$ apt search curl
Sorting... Done
Full Text Search... Done
curl/jammy-updates,jammy-security,now 7.81.0-1ubuntu1.6 amd64
  command line tool for transferring data with URL syntax

jammy-updates にあるので、ここから Package.gz を取得する。

$ wget -O - http://archive.ubuntu.com/ubuntu/dists/jammy-updates/main/binary-amd64/Packages.gz && gzip -d Packages.gz

ここから curl の情報を探すと、以下の内容が見つかる。

Package: curl
Architecture: amd64
Version: 7.81.0-1ubuntu1.6
Multi-Arch: foreign
Priority: optional
Section: web
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Alessandro Ghedini <ghedo@debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 442
Depends: libc6 (>= 2.34), libcurl4 (= 7.81.0-1ubuntu1.6), zlib1g (>= 1:1.1.4)
Filename: pool/main/c/curl/curl_7.81.0-1ubuntu1.6_amd64.deb
Size: 194098
MD5sum: 96d7e93eb6b235c61151602cd0ae64d5
SHA1: 363da17b0cec5a35d1fc40ea9169c37bd633cd77
SHA256: 0b2e456beac4af7256c23c596f94093290b6cd32ce3c04bc27bdd3aa51589b2b
SHA512: afba68768e215d9e45eaf71484c1183f712a6981f66d9b29bef67f300d2be8e487f53d0fad280361c854008375d34796cdca2d62254e3c7de9f03295d29fd91c
Homepage: https://curl.haxx.se
Description: command line tool for transferring data with URL syntax
Task: server-minimal, cloud-image, ubuntu-wsl, server, ubuntu-server-raspi, ubuntustudio-publishing, ubuntu-budgie-desktop, ubuntu-budgie-desktop-raspi
Description-md5: f83293d10df083ae6f7bb7d01642913c
$ wget http://archive.ubuntu.com/ubuntu/pool/main/c/curl/curl_7.81.0-1ubuntu1.6_amd64.deb

.deb ファイルは、Debian のパッケージアーカイブなので、ar コマンドを使ってこれを展開する。

$ ar x curl_7.81.0-1ubuntu1.6_amd64.deb

以下のファイルが手に入る。

.
├── control.tar.zst
├── data.tar.zst
└── debian-binary

さらに data.tar.zst を展開する。

$ zstd -d ./data.tar.zst
$ tar -xf data.tar
./usr
├── bin
│   └── curl
└── share
    ├── doc
    │   └── curl
    │       ├── changelog.Debian.gz -> ../libcurl4/changelog.Debian.gz
    │       ├── copyright
    │       └── NEWS.Debian.gz -> ../libcurl4/NEWS.Debian.gz
    ├── man
    │   └── man1
    │       └── curl.1.gz
    └── zsh
        └── vendor-completions
            └── _curl

bin/curl が求めるファイル。

$ ./bin/curl http://example.com
<!doctype html>
<html>
<head>
    <title>Example Domain</title>

    <meta charset="utf-8" />
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style type="text/css">
    body {
        background-color: #f0f0f2;
        margin: 0;
        padding: 0;
        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
        
    }
    div {
        width: 600px;
        margin: 5em auto;
        padding: 2em;
        background-color: #fdfdff;
        border-radius: 0.5em;
        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
    }
    a:link, a:visited {
        color: #38488f;
        text-decoration: none;
    }
    @media (max-width: 700px) {
        div {
            margin: 0 auto;
            width: auto;
        }
    }
    </style>    
</head>

<body>
<div>
    <h1>Example Domain</h1>
    <p>This domain is for use in illustrative examples in documents. You may use this
    domain in literature without prior coordination or asking for permission.</p>
    <p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>

パッケージの署名検証 🔗

多くの場合、パッケージの取得は公衆網を経由して行われるため、経路のどこかに悪意のある第三者が不正なパッケージを目的のパッケージと偽って返してくる場合を防ぐための検証が必須。

curl_7.81.0-1ubuntu1.6_amd64.deb のハッシュをとってみると、

$ md5sum ./curl_7.81.0-1ubuntu1.6_amd64.deb 
96d7e93eb6b235c61151602cd0ae64d5  ./curl_7.81.0-1ubuntu1.6_amd64.deb

$ sha1sum ./curl_7.81.0-1ubuntu1.6_amd64.deb 
363da17b0cec5a35d1fc40ea9169c37bd633cd77  ./curl_7.81.0-1ubuntu1.6_amd64.deb

$ sha256sum ./curl_7.81.0-1ubuntu1.6_amd64.deb 
0b2e456beac4af7256c23c596f94093290b6cd32ce3c04bc27bdd3aa51589b2b  ./curl_7.81.0-1ubuntu1.6_amd64.deb

$ sha512sum ./curl_7.81.0-1ubuntu1.6_amd64.deb 
afba68768e215d9e45eaf71484c1183f712a6981f66d9b29bef67f300d2be8e487f53d0fad280361c854008375d34796cdca2d62254e3c7de9f03295d29fd91c  ./curl_7.81.0-1ubuntu1.6_amd64.deb

となり、いずれも Packages に記載されていた値と一致している。
なので、「Packages ファイルが正であれば、取得したパッケージ(curl_7.81.0-1ubuntu1.6_amd64.deb)は正」となる。

なので次は「Packages ファイルが正」を保証する仕組みが必要で、これには InRelease (または ReleaseRelease.gpg) ファイルが使われる。
このファイルはリポジトリごとに配布され、上記の jammy-updates リポジトリだと ここ にある。

先ほど落としてきた Packages.gz については、以下のように書かれている。

45930b1b2b6c9845a326ad70ed7c9b70           971597 main/binary-amd64/Packages.gz

Packages.gz の MD5sum をとってみると、一致していることがわかる。

$ md5sum Packages.gz 
45930b1b2b6c9845a326ad70ed7c9b70  Packages.gz

ここまでで、

  • Packages ファイルが正であれば、取得したパッケージ(curl_7.81.0-1ubuntu1.6_amd64.deb)は正
  • InRelease ファイルが正であれば、Packages ファイルは正

が成り立っているので、次は InRelease ファイルの正当性を検証する方法が必要になる。

ここまで見てきた通り、公衆網からファイル取得する限り、そのファイルの正当性を保証する別のファイルが必要になる。
このことから、最終的には「信頼の根っこ」にあたる、OS に初めからバンドルされた何かをもとに正当性の検証ができる必要性が示唆され、

InRelease ファイルの正当性の検証には、そのように実施される。 具体的には、OS のインストーラに同梱されている GPG 公開鍵(/etc/apt/trusted.gpg{,.d/*.gpg}: 記事の冒頭で warning が出ていた場所)を信頼することとして、 DSA によって署名検証を行う。

$ gpg -a --list-keys --keyring /etc/apt/trusted.gpg
/etc/apt/trusted.gpg
--------------------
pub   rsa4096 2017-02-22 [SCEA]
      9DC858229FC7DD38854AE2D88D81803C0EBFCD88
uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]

この公開鍵を使って、ダウンロードしてきた InRelease の検証を行う。

$ gpg --keyring /etc/apt/trusted.gpg.d/ubuntu-keyring-2018-archive.gpg --verify InRelease
gpg: Signature made Fri 30 Dec 2022 08:57:05 AM UTC
gpg:                using RSA key 871920D1991BC93C
gpg: Good signature from "Ubuntu Archive Automatic Signing Key (2018) <ftpmaster@ubuntu.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: F6EC B376 2474 EDA9 D21B  7022 8719 20D1 991B C93C

これで、無事 package の正当性の確認が取れた。
記事の冒頭で出ていた warning は、apt-key が単一の GPG keyring に複数の異なるリポジトリの鍵を一括で管理する設計になっており、セキュリティ上の懸念があるため Deprecate されたために出ていたものになる。