Reise in die Mitte ... Hafenarbeiter Bild. Oder wie man ein Bild aus der Registrierung ohne Andockfenster herunterlädt

3 Tage vor dem neuen Jahr gab es eine Aufgabe, unsere Software über den Manager auf einem Flash-Laufwerk auf den Client zu übertragen. Software ist eine Microservice-Plattform mit mehreren Dutzend Docker-Bildern mit vielen Einstellungen und einer kilometerlangen Helmkarte. Was wir hatten:


  • Manager in Moskau (ich komme nicht von dort)
  • Windows
  • Es gibt keine direkte Interaktion (und wenn ja, hat es nicht wirklich geholfen)
  • Hafenarbeiter Nr

Pff, dachte ich! Ich nehme Golang, schreibe ein Programm, kompiliere für Windows.
... und 5 Stunden später erkannte ich die Eile meiner Schlussfolgerungen. In diesem Moment erinnerte sich Nelsons Lachen zum ersten Mal. HA HA! Das hat mich die ganze Zeit verfolgt, in der ich mich mit dem Thema befasst habe.


Die meisten Beispiele, die ich gefunden habe, erfordern dockerd. Zwei Skripte ohne Dockerd, die nach einer Stunde googeln gefunden wurden, eins und zwei . Die erste Option hat mir geholfen, den Vorgang des Erlangens aller Bildebenen und Konfigurationsdateien zu verstehen, aber es ist unmöglich, sie mit Windows zu verwenden. Und die zweite Option zeigte an, dass nicht nur verschiedene Hashes auf dem Bildschirm flackerten, speziell dieses FIXME . Man könnte natürlich dort aufhören, es funktioniert! Transfer zum Mitnehmen ist nicht schwierig. Aber wie kann man überprüfen, ob die Bilder des Managers dieselbe Form haben wie in unserer Registrierung? Aber auf keinen Fall! Aus diesem Grund habe ich nur Bilder in den freigegebenen Speicher hochgeladen, mit dem Befehl docker save heruntergeladen und den Link freigegeben. Und beruhigte sich darüber.


Am vierten Tag der Ferien, ziemlich müde von ihnen, überfiel mich die Idee, das Docker-Image korrekt herunterzuladen und zusammenzusetzen, wieder und ich tauchte für ein paar Stunden in den Moby- Code ein.


Was ich diesmal hatte:


  • Verstehen, wie man alle Schichten erhält

Ich habe Python "in die Hand genommen" und basierend auf diesem Skript beschlossen, es zu beheben. Am zweiten Tag beschloss ich, ein Drehbuch von Grund auf neu zu schreiben. Ich erinnerte mich an mein Handwerk zur Autorisierung und kopierte einfach einen Teil des Codes von dort sowie die Entwicklungen, die ich bereits bei der Bearbeitung des Skripts gemacht hatte. Es gab keine Probleme mit der Autorisierung und dem Herunterladen dieser Daten, aber es stellten sich Fragen:


  • Welche Hashes werden angezeigt, wenn der Docker Pull-Befehl ausgeführt wird?
  • Mit welchen Hashes werden Verzeichnisse im Image-Tarball benannt?
  • Wie erstelle ich ein Tar-Archiv, damit die Prüfsumme mit dem Original-Image übereinstimmt?

Für das Studium habe ich ein Ubuntu-Bild ausgewählt: 18.04
sha256sum Bild über docker save gespeichert docker save - 257cab9137419a53359d0ed76f680fe926ed3645238357bdcdb84070a8f26cd0.


 > docker pull ubuntu:18.04 18.04: Pulling from library/ubuntu 2746a4a261c9: Downloading [==============> ] 6.909MB/26.69MB 4c1d20cdee96: Download complete 0d3160e1d0de: Download complete c8e37668deea: Download complete Digest: sha256:250cc6f3f3ffc5cdaa9d8f4946ac79821aafb4d3afc93928f0de9336eba21aa4 Status: Downloaded newer image for ubuntu:18.04 docker.io/library/ubuntu:18.04 

Bild-Tarball-Inhalt


 tar tvf ubuntu.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/ -rw-r--r-- 0 root root 3 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/VERSION -rw-r--r-- 0 root root 477 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/json -rw-r--r-- 0 root root 991232 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/layer.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/ -rw-r--r-- 0 root root 3 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/VERSION -rw-r--r-- 0 root root 477 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/json -rw-r--r-- 0 root root 15872 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/layer.tar -rw-r--r-- 0 root root 3411 Dec 19 11:21 549b9b86cb8d75a2b668c21c50ee092716d070f129fd1493f95ab7e43767eab8.json drwxr-xr-x 0 root root 0 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/ -rw-r--r-- 0 root root 3 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/VERSION -rw-r--r-- 0 root root 1264 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/json -rw-r--r-- 0 root root 3072 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/layer.tar drwxr-xr-x 0 root root 0 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/ -rw-r--r-- 0 root root 3 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/VERSION -rw-r--r-- 0 root root 401 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/json -rw-r--r-- 0 root root 65571328 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/layer.tar -rw-r--r-- 0 root root 432 Jan 1 1970 manifest.json -rw-r--r-- 0 root root 88 Jan 1 1970 repositories 

Bildkonfiguration


 { ... "rootfs": { "type": "layers", "diff_ids": [ "sha256:2dc9f76fb25b31e0ae9d36adce713364c682ba0d2fa70756486e5cedfaf40012", "sha256:9f3bfcc4a1a8a676da07287a1aa6f2dcc8e869ea6f054c337593481a5bb1345e", "sha256:27dd43ea46a831c39d224e7426794145fba953cd7309feccf4d5ea628072f6a2", "sha256:918efb8f161b4cbfa560e00e8e0efb737d7a8b00bf91bb77976257cd0014b765" ] } ... } 

Die erste Frage wurde schnell genug geklärt, die Dokumentation https://github.com/opencontainers/image-spec/blob/master/config.md half. Die Hashes, die angezeigt werden, wenn Sie den Docker-Pull-Befehl ausführen, sind chainIDs, die aus der diff_ids-Liste des Image-Config-Manifests berechnet werden, wobei die erste chainID immer die erste aus der diff_ids-Liste ist und die nachfolgenden Hashsummen aus der Zeichenfolge (chain_id[i-1] + " " + diff_id[i]) . Code zum Erstellen von chainID chainID:


 def chain_ids(ids: list) -> list: chain = list() chain.append(ids[0]) if len(ids) < 2: return ids nxt = list() nxt.append("sha256:" + hashlib.sha256(f'{ids[0]} {ids[1]}'.encode()).hexdigest()) nxt.extend(ids[2:]) chain.extend(chain_ids(nxt)) return chain 

Das Hinzufügen eines Präfixes mit dem Namen des Algorithmus, in diesem Fall "sha256:", ist obligatorisch und gehört zu den Anforderungen des opencontainers-Standards, d.h. Die Zeichenfolge sollte die Form "Algorithmus: Hash" haben.


Er verbrachte zwei Abende mit der Frage nach der Benennung eines Verzeichnisses. Lange genug habe ich mir den Quellcode von docker-daemon und Oh! ein wunder! Ich habe es geschafft, den Generierungscode hier und hier zu finden . Um den Verzeichnisnamen zu generieren, müssen Sie den Hash aus der Konfiguration der JSON-Ebene berechnen. Docker hat mehrere Konfigurationsversionen und vor Docker Engine 1.9 wurden Konfigurationen der Version v1 verwendet. Gesagt, getan! Und wieder entsteht die Silhouette von Nelson. Nach einem kurzen Debug wurde mir klar, dass sich das Problem in der Generation von Json versteckt. In Python kann sich die Reihenfolge der Daten in einem Wörterbuch von der Reihenfolge der Daten in json unterscheiden, die aus diesem Wörterbuch generiert wurden. Die Reihenfolge der Daten in json ist unterschiedlich, und dementsprechend ist auch der Hash unterschiedlich. Ich musste zu OrderedDict gehen und dort die gewünschte Datenreihenfolge vorregistrieren. Dies erhöhte die Codegröße um das Eineinhalbfache.


Es scheint, dass alles korrigiert ist, ich führe das Skript aus und ... irgendwo tief im Inneren taucht das berüchtigte HA-HA auf! Der letzte Hash stimmt nicht überein. Ich studiere noch einmal den Code und sehe , und dies ist eine andere v1-config, die alle Informationen über das Image enthält, die mit dem docker inspect . Fügen Sie ein weiteres OrderDict speziell für ihn hinzu, ergänzen Sie den Code und ... HA HA!
Es war bereits 5 Uhr morgens und mein Kopf dachte nicht wirklich nach, also kehrte ich nach dem Schlafen zum Anzeigen des Codes zurück. Beim wiederholten Betrachten des Generierungscodes wurde eine Zeile angezeigt . Wie froh, dass ich das gesehen habe. Bevor ich sie sah, dachte ich daran, meinen Hafenarbeiter damit zu bauen Black Jack und Datenerfassung. Ich schalte Debug ein, führe den docker save und ... es ist überhaupt nicht lustig, in docker-desktop für Mac OS gibt es eine Begrenzung der Zeilenlänge im Protokoll von 947 Zeichen und die generierte Konfiguration bricht auf \" . Nachdem ich alle diese Schritte in Linux ausgeführt habe, habe ich es geschafft, die Konfiguration zu erhalten Ebene der ersten Version, auf deren Basis ich den Code geschrieben habe, und ich habe es geschafft, den gewünschten Hash der letzten Ebene zu erhalten. Die Hashs für alle Dateien sind die gleichen, die Verzeichnisse werden auf die gleiche Weise benannt wie das Original-Image. Es ist Zeit, ein Tar-Archiv zu erstellen ... HAHA!


Die Dateigröße stimmt nicht überein, lesen Sie https://github.com/opencontainers/image-spec/blob/master/layer.md und das tar-Archivformat . Der Standardwert ist 10240 Bytes und die Größe des von mir gesammelten Archivs ist 9216 Bytes größer. Zuerst dachte ich, es sei notwendig, die Blockgröße auf 1024 Bytes zu reduzieren, was sich als falsch herausstellte. Infolgedessen entsprach die Blockgröße von 512 Bytes der Größe der Archive.


 tarfile.RECORDSIZE = 512 

Die erste Zeile des neu erstellten Archivs enthält den Stammordner "/". Diese Option ist nicht geeignet, daher ergänze ich den Code, indem ich den Inhalt des Ordners scanne und ihn einzeln zum Archiv hinzufüge, nachdem ich ihn zuvor sortiert habe.


Schließlich ist es uns gelungen, die gleiche Dateigröße und ein einheitliches Erscheinungsbild der Verzeichnisse zu erzielen, aber das ist noch nicht alles. Dateien und Verzeichnisse müssen mit Ausnahme von manifest.json und respoitories die Attribute st_atime im Archiv haben, st_mtime gleich st_ctime. Für manifest.json- und respoitories-Dateien müssen die Attribute st_atime, st_mtime und st_ctime auf den Beginn der Ära 1970-01-01 00:00 datiert sein. Alle Daten müssen entsprechend der Zeitzone eingestellt werden. Da ich alle Arbeiten unter Mac OS ausgeführt habe, ist mir ein Unterschied aufgefallen. Beim Speichern des Images unter Linux sah die Liste der Dateien im Archiv folgendermaßen aus:


 tar tvf ubuntu.tar drwxr-xr-x 0/0 0 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/ -rw-r--r-- 0/0 3 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/VERSION -rw-r--r-- 0/0 477 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/json -rw-r--r-- 0/0 991232 Dec 19 11:21 07adecfcb06a1142a69c3e769cb38f2d4ef9d772726ce1e65bc6dbd4448cccc9/layer.tar drwxr-xr-x 0/0 0 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/ -rw-r--r-- 0/0 3 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/VERSION -rw-r--r-- 0/0 477 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/json -rw-r--r-- 0/0 15872 Dec 19 11:21 3e1d90747aa9d2a7ec6e9693bdd490dff8528b9aec4a2fac2300824e4ba3a60e/layer.tar -rw-r--r-- 0/0 3411 Dec 19 11:21 549b9b86cb8d75a2b668c21c50ee092716d070f129fd1493f95ab7e43767eab8.json drwxr-xr-x 0/0 0 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/ -rw-r--r-- 0/0 3 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/VERSION -rw-r--r-- 0/0 1264 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/json -rw-r--r-- 0/0 3072 Dec 19 11:21 b0474230e27ddbba2f46397aac85d4d2fd748064ed9c0ff1e57fec4f063fcf6b/layer.tar drwxr-xr-x 0/0 0 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/ -rw-r--r-- 0/0 3 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/VERSION -rw-r--r-- 0/0 401 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/json -rw-r--r-- 0/0 65571328 Dec 19 11:21 c8ba25f7db9f70220ac92449b238a0697f9eb580ef4f905225a333fc0a5e8719/layer.tar -rw-r--r-- 0/0 432 Jan 1 1970 manifest.json -rw-r--r-- 0/0 88 Jan 1 1970 repositories 

Im Gegensatz zu der Liste am Anfang des Artikels wird das Archiv unter Linux mit dem Nur-Zahlen-Flag gespeichert. Das dafür verantwortliche tarinfo-Objekt enthält zwei Variablen, tarinfo.uname und tarinfo.gname. Das zweite Problem bei Mac OS ist das Fehlen einer Stammgruppe. Es wird mit der Variablen tarinfo.gid im selben tarinfo-Objekt behoben. Nun, alles scheint ein Archiv zu schaffen ...



Für alle Dateien konvergiert der Hash, die Namen der Verzeichnisse und Dateien sind gleich, die Attribute st_atime, st_mtime und st_ctime konvergieren mit dem Original, die Rechte an den Dateien sind exakt gleich. Beim Öffnen beider Archive in einem Hex-Editor wurde ein kleiner Unterschied festgestellt:



Das obere Fenster ist das Original, das untere Archiv habe ich gesammelt.


Das tar-Format verstehen. Nach dem Namen des Verzeichnisses steht der Wert der Rechte an der Datei (oranges Rechteck). Der Unterschied besteht darin, dass Informationen über das zum Archiv hinzugefügte Objekt nicht zu den Verzeichnisrechten hinzugefügt werden. Ein in den Rechten angegebener Wert von 40755 gibt an, dass dies ein Verzeichnis mit Berechtigungen von 755 ist, und 100644 ist eine Datei mit Rechten von 644. Das rote Rechteck ist ein magischer String und wird nach dem Tarfile-Code, dem magischen String ustar\000 , nur in den Formaten PAX und USTAR verwendet. Das PAX-Format passt überhaupt nicht, es verwendet eine spezielle Art von Header. Das blaue Rechteck ist eine Prüfsumme und unterscheidet sich durch die Verwendung unterschiedlicher Formate zum Schreiben von Rechten auf eine Datei und Magic-Header.


Ich ändere das Archivformat auf USTAR, aber es ist nicht klar, was ich mit den Dateiberechtigungen anfangen soll. Hier und hier passiert Magie für mich, ich habe noch nie mit dem Oktalsystem gearbeitet und ich verstehe nicht, warum das kaufmännische Und hier benötigt wird (jeder kann sein Wissen in den Kommentaren teilen). Ich musste mehrere Drucke hinzufügen, um zu sehen, welche Daten mit Argumenten ankommen und welche Daten zur Bildung des Archivblocks verwendet werden. Nimmt man eine Ganzzahl von der zweiten Position des Datenblocks, war es 16877, und führt sie zur Oktalrechnung, stellt sich heraus, dass dieser Wert 0o40755 , was 0o40755 das ist 0o40755 was ich brauche. Nachdem ich nur die Funktionen get_info und _create_header überschrieben und & 0o7777 aus ihnen & 0o7777 hatte (mir fiel nichts anderes ein), gelang es mir, ein tar-Archiv mit einem sha256-Hash zu sammeln, der mit dem ursprünglichen übereinstimmte.


PS Beim Schreiben eines Artikels wurde das Bild von Ubuntu aktualisiert: 18.04 auf hub.docker.com. Also musste ich das Bild auf Digest herunterladen. Die Hash-Summen stimmen nicht mehr mit dem Original überein, da Digest anstelle des Tags geschrieben wurde, da es sich ansonsten um identische Bilder handelte.


Die zweite Entdeckung für mich war das Fehlen einer Repository-Datei im Archiv, wenn ein Bild mit einem fehlenden Tag mit dem docker save gespeichert wurde.


Voller Arbeitscode hier: https://github.com/myback/docker_pull


Bild von Nelson Manz, sowie sein Lachen "HA-HA" ist Eigentum von FOX :)

Source: https://habr.com/ru/post/de484374/


All Articles