occystrap 0.3.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. occystrap/_version.py +34 -0
  2. occystrap/filters/__init__.py +10 -0
  3. occystrap/filters/base.py +67 -0
  4. occystrap/filters/exclude.py +136 -0
  5. occystrap/filters/inspect.py +179 -0
  6. occystrap/filters/normalize_timestamps.py +123 -0
  7. occystrap/filters/search.py +177 -0
  8. occystrap/inputs/__init__.py +1 -0
  9. occystrap/inputs/base.py +40 -0
  10. occystrap/inputs/docker.py +171 -0
  11. occystrap/inputs/registry.py +260 -0
  12. occystrap/inputs/tarfile.py +88 -0
  13. occystrap/main.py +330 -31
  14. occystrap/outputs/__init__.py +1 -0
  15. occystrap/outputs/base.py +46 -0
  16. occystrap/{output_directory.py → outputs/directory.py} +10 -9
  17. occystrap/outputs/docker.py +137 -0
  18. occystrap/{output_mounts.py → outputs/mounts.py} +2 -1
  19. occystrap/{output_ocibundle.py → outputs/ocibundle.py} +1 -1
  20. occystrap/outputs/registry.py +240 -0
  21. occystrap/{output_tarfile.py → outputs/tarfile.py} +18 -2
  22. occystrap/pipeline.py +297 -0
  23. occystrap/tarformat.py +122 -0
  24. occystrap/tests/test_inspect.py +355 -0
  25. occystrap/tests/test_tarformat.py +199 -0
  26. occystrap/uri.py +231 -0
  27. occystrap/util.py +67 -38
  28. occystrap-0.4.1.dist-info/METADATA +444 -0
  29. occystrap-0.4.1.dist-info/RECORD +38 -0
  30. {occystrap-0.3.0.dist-info → occystrap-0.4.1.dist-info}/WHEEL +1 -1
  31. {occystrap-0.3.0.dist-info → occystrap-0.4.1.dist-info}/entry_points.txt +0 -1
  32. occystrap/docker_extract.py +0 -36
  33. occystrap/docker_registry.py +0 -192
  34. occystrap-0.3.0.dist-info/METADATA +0 -131
  35. occystrap-0.3.0.dist-info/RECORD +0 -20
  36. occystrap-0.3.0.dist-info/pbr.json +0 -1
  37. {occystrap-0.3.0.dist-info → occystrap-0.4.1.dist-info/licenses}/AUTHORS +0 -0
  38. {occystrap-0.3.0.dist-info → occystrap-0.4.1.dist-info/licenses}/LICENSE +0 -0
  39. {occystrap-0.3.0.dist-info → occystrap-0.4.1.dist-info}/top_level.txt +0 -0
@@ -1,192 +0,0 @@
1
- # A simple implementation of a docker registry client. Fetches an image to a tarball.
2
- # With a big nod to https://github.com/NotGlop/docker-drag/blob/master/docker_pull.py
3
-
4
- # https://docs.docker.com/registry/spec/manifest-v2-2/ documents the image manifest
5
- # format, noting that the response format you get back varies based on what you have
6
- # in your accept header for the request.
7
-
8
- import hashlib
9
- import io
10
- import logging
11
- import os
12
- import re
13
- import sys
14
- import tempfile
15
- import zlib
16
-
17
- from occystrap import constants
18
- from occystrap import util
19
-
20
- LOG = logging.getLogger(__name__)
21
- LOG.setLevel(logging.INFO)
22
-
23
- DELETED_FILE_RE = re.compile(r'.*/\.wh\.(.*)$')
24
-
25
-
26
- def always_fetch():
27
- return True
28
-
29
-
30
- class Image(object):
31
- def __init__(self, registry, image, tag, os='linux', architecture='amd64', variant='',
32
- secure=True):
33
- self.registry = registry
34
- self.image = image
35
- self.tag = tag
36
- self.os = os
37
- self.architecture = architecture
38
- self.variant = variant
39
- self.secure = secure
40
-
41
- self._cached_auth = None
42
-
43
- def request_url(self, method, url, headers=None, data=None, stream=False):
44
- if not headers:
45
- headers = {}
46
-
47
- if self._cached_auth:
48
- headers.update({'Authorization': 'Bearer %s' % self._cached_auth})
49
-
50
- try:
51
- return util.request_url(method, url, headers=headers, data=data,
52
- stream=stream)
53
- except util.UnauthorizedException as e:
54
- auth_re = re.compile('Bearer realm="([^"]*)",service="([^"]*)"')
55
- m = auth_re.match(e.args[5].get('Www-Authenticate'))
56
- if m:
57
- auth_url = ('%s?service=%s&scope=repository:%s:pull'
58
- % (m.group(1), m.group(2), self.image))
59
- r = util.request_url('GET', auth_url)
60
- token = r.json().get('token')
61
- headers.update({'Authorization': 'Bearer %s' % token})
62
- self._cached_auth = token
63
-
64
- return util.request_url(
65
- method, url, headers=headers, data=data, stream=stream)
66
-
67
- def fetch(self, fetch_callback=always_fetch):
68
- LOG.info('Fetching manifest')
69
- moniker = 'https'
70
- if not self.secure:
71
- moniker = 'http'
72
-
73
- r = self.request_url(
74
- 'GET',
75
- '%(moniker)s://%(registry)s/v2/%(image)s/manifests/%(tag)s'
76
- % {
77
- 'moniker': moniker,
78
- 'registry': self.registry,
79
- 'image': self.image,
80
- 'tag': self.tag
81
- },
82
- headers={'Accept': ('application/vnd.docker.distribution.manifest.v2+json,'
83
- 'application/vnd.docker.distribution.manifest.list.v2+json')})
84
-
85
- config_digest = None
86
- if r.headers['Content-Type'] == 'application/vnd.docker.distribution.manifest.v2+json':
87
- manifest = r.json()
88
- config_digest = manifest['config']['digest']
89
- elif r.headers['Content-Type'] == 'application/vnd.docker.distribution.manifest.list.v2+json':
90
- for m in r.json()['manifests']:
91
- if 'variant' in m['platform']:
92
- LOG.info('Found manifest for %s on %s %s'
93
- % (m['platform']['os'], m['platform']['architecture'],
94
- m['platform']['variant']))
95
- else:
96
- LOG.info('Found manifest for %s on %s'
97
- % (m['platform']['os'], m['platform']['architecture']))
98
-
99
- if (m['platform']['os'] == self.os and
100
- m['platform']['architecture'] == self.architecture and
101
- m['platform'].get('variant', '') == self.variant):
102
- LOG.info('Fetching matching manifest')
103
- r = self.request_url(
104
- 'GET',
105
- '%(moniker)s://%(registry)s/v2/%(image)s/manifests/%(tag)s'
106
- % {
107
- 'moniker': moniker,
108
- 'registry': self.registry,
109
- 'image': self.image,
110
- 'tag': m['digest']
111
- },
112
- headers={'Accept': ('application/vnd.docker.distribution.manifest.v2+json')})
113
- manifest = r.json()
114
- config_digest = manifest['config']['digest']
115
-
116
- if not config_digest:
117
- raise Exception('Could not find a matching manifest for this '
118
- 'os / architecture / variant')
119
- else:
120
- raise Exception('Unknown manifest content type %s!' %
121
- r.headers['Content-Type'])
122
-
123
- LOG.info('Fetching config file')
124
- r = self.request_url(
125
- 'GET',
126
- '%(moniker)s://%(registry)s/v2/%(image)s/blobs/%(config)s'
127
- % {
128
- 'moniker': moniker,
129
- 'registry': self.registry,
130
- 'image': self.image,
131
- 'config': config_digest
132
- })
133
- config = r.content
134
- h = hashlib.sha256()
135
- h.update(config)
136
- if h.hexdigest() != config_digest.split(':')[1]:
137
- LOG.error('Hash verification failed for image config blob (%s vs %s)'
138
- % (config_digest.split(':')[1], h.hexdigest()))
139
- sys.exit(1)
140
-
141
- config_filename = ('%s.json' % config_digest.split(':')[1])
142
- yield (constants.CONFIG_FILE, config_filename,
143
- io.BytesIO(config))
144
-
145
- LOG.info('There are %d image layers' % len(manifest['layers']))
146
- for layer in manifest['layers']:
147
- layer_filename = layer['digest'].split(':')[1]
148
- if not fetch_callback(layer_filename):
149
- LOG.info('Fetch callback says skip layer %s' % layer['digest'])
150
- yield (constants.IMAGE_LAYER, layer_filename, None)
151
- continue
152
-
153
- LOG.info('Fetching layer %s (%d bytes)'
154
- % (layer['digest'], layer['size']))
155
- r = self.request_url(
156
- 'GET',
157
- '%(moniker)s://%(registry)s/v2/%(image)s/blobs/%(layer)s'
158
- % {
159
- 'moniker': moniker,
160
- 'registry': self.registry,
161
- 'image': self.image,
162
- 'layer': layer['digest']
163
- },
164
- stream=True)
165
-
166
- # We can use zlib for streaming decompression, but we need to tell it
167
- # to ignore the gzip header which it doesn't understand. Unfortunately
168
- # tarfile doesn't do streaming writes (and we need to know the
169
- # decompressed size before we can write to the tarfile), so we stream
170
- # to a temporary file on disk.
171
- try:
172
- h = hashlib.sha256()
173
- d = zlib.decompressobj(16 + zlib.MAX_WBITS)
174
-
175
- with tempfile.NamedTemporaryFile(delete=False) as tf:
176
- LOG.info('Temporary file for layer is %s' % tf.name)
177
- for chunk in r.iter_content(8192):
178
- tf.write(d.decompress(chunk))
179
- h.update(chunk)
180
-
181
- if h.hexdigest() != layer_filename:
182
- LOG.error('Hash verification failed for layer (%s vs %s)'
183
- % (layer_filename, h.hexdigest()))
184
- sys.exit(1)
185
-
186
- with open(tf.name, 'rb') as f:
187
- yield (constants.IMAGE_LAYER, layer_filename, f)
188
-
189
- finally:
190
- os.unlink(tf.name)
191
-
192
- LOG.info('Done')
@@ -1,131 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: occystrap
3
- Version: 0.3.0
4
- Summary: occystrap: docker and OCI container tools
5
- Home-page: https://github.com/shakenfist/occystrap
6
- Author: Michael Still
7
- Author-email: mikal@stillhq.com
8
- License: Apache2
9
- Platform: UNKNOWN
10
- Classifier: Intended Audience :: Information Technology
11
- Classifier: Intended Audience :: System Administrators
12
- Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Operating System :: POSIX :: Linux
14
- Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.7
17
- Description-Content-Type: text/markdown
18
- Requires-Dist: click (>=7.1.1)
19
- Requires-Dist: oslo.concurrency
20
- Requires-Dist: pbr
21
- Requires-Dist: prettytable
22
- Requires-Dist: requests
23
- Requires-Dist: shakenfist-utilities
24
-
25
- # Occy Strap
26
-
27
- Occy Strap is a simple set of Docker and OCI container tools, which can be used either for container forensics or for implementing an OCI orchestrator, depending on your needs. This is a very early implementation, so be braced for impact.
28
-
29
- ## Downloading an image from a repository and storing as a tarball
30
-
31
- Let's say we want to download an image from a repository and store it as a local tarball. This is a common thing to want to do in airgapped environments for example. You could do this with docker with a `docker pull; docker save`. The Occy Strap equivalent is:
32
-
33
- ```
34
- occystrap fetch-to-tarfile registry-1.docker.io library/busybox latest busybox.tar
35
- ```
36
-
37
- In this example we're pulling from the Docker Hub (registry-1.docker.io), and are downloading busybox's latest version into a tarball named `busybox-occy.tar`. This tarball can be loaded with `docker load -i busybox.tar` on an airgapped Docker environment.
38
-
39
- ## Downloading an image from a repository and storing as an extracted tarball
40
-
41
- The format of the tarball in the previous example is two JSON configuration files and a series of image layers as tarballs inside the main tarball. You can write these elements to a directory instead of to a tarball if you'd like to inspect them. For example:
42
-
43
- ```
44
- occystrap fetch-to-extracted registry-1.docker.io library/centos 7 centos7
45
- ```
46
-
47
- This example will pull from the Docker Hub the Centos image with the label "7", and write the content to a directory in the current working directory called "centos7". If you tarred centos7 like this, you'd end up with a tarball equivalent to what `fetch-to-tarfile` produces, which could therefore be loaded with `docker load`:
48
-
49
- ```
50
- cd centos7; tar -cf ../centos7.tar *
51
- ```
52
-
53
- ## Downloading an image from a repository and storing it in a merged directory
54
-
55
- In scenarios where image layers are likely to be reused between images (for example many images which share a common base layer), you can save disk space by downloading images to a directory which contains more than one image. To make this work, you need to instruct Occy Strap to use unique names for the JSON elements within the image file:
56
-
57
- ```
58
- occystrap fetch-to-extracted --use-unique-names registry-1.docker.io \
59
- homeassistant/home-assistant latest merged_images
60
- occystrap fetch-to-extracted --use-unique-names registry-1.docker.io \
61
- homeassistant/home-assistant stable merged_images
62
- occystrap fetch-to-extracted --use-unique-names registry-1.docker.io \
63
- homeassistant/home-assistant 2021.3.0.dev20210219 merged_images
64
- ```
65
-
66
- Each of these images include 21 layers, but the merged_images directory at the time of writing this there are 25 unique layers in the directory. You end up with a layout like this:
67
-
68
- ```
69
- 0465ae924726adc52c0216e78eda5ce2a68c42bf688da3f540b16f541fd3018c
70
- 10556f40181a651a72148d6c643ac9b176501d4947190a8732ec48f2bf1ac4fb
71
- ...
72
- catalog.json
73
- cd8d37c8075e8a0195ae12f1b5c96fe4e8fe378664fc8943f2748336a7d2f2f3
74
- d1862a2c28ec9e23d88c8703096d106e0fe89bc01eae4c461acde9519d97b062
75
- d1ac3982d662e038e06cc7e1136c6a84c295465c9f5fd382112a6d199c364d20.json
76
- ...
77
- d81f69adf6d8aeddbaa1421cff10ba47869b19cdc721a2ebe16ede57679850f0.json
78
- ...
79
- manifest-homeassistant_home-assistant-2021.3.0.dev20210219.json
80
- manifest-homeassistant_home-assistant-latest.json
81
- manifest-homeassistant_home-assistant-stable.json
82
- ```
83
-
84
- `catalog.json` is an Occy Strap specific artefact which maps which layers are used by which image. Each of the manifest files for the various images have been converted to have a unique name instead of `manifest.json` as well.
85
-
86
- To extract a single image from such a shared directory, use the `recreate-image` command:
87
-
88
- ```
89
- occystrap recreate-image merged_images homeassistant/home-assistant latest ha-latest.tar
90
- ```
91
-
92
- ## Exploring the contents of layers and overwritten files
93
-
94
- Similarly, if you'd like the layers to be expanded from their tarballs to the filesystem, you can pass the `--expand` argument to `fetch-to-extracted` to have them extracted. This will also create a filesystem at the name of the manifest which is the final state of the image (the layers applied sequential). For example:
95
-
96
- ```
97
- occystrap fetch-to-extracted --expand quay.io \
98
- ukhomeofficedigital/centos-base latest ukhomeoffice-centos
99
- ```
100
-
101
- Note that layers delete files from previous layers with files named ".wh.$previousfilename". These files are _not_ processed in the expanded layers, so that they are visible to the user. They are however processed in the merged layer named for the manifest file.
102
-
103
- ## Generating an OCI runtime bundle
104
-
105
- This isn't fully supported yet, but you can extract an image to an OCI image bundle
106
- with the following command:
107
-
108
- ```
109
- occystrap fetch-to-oci registry-1.docker.io library/hello-world latest bar
110
- ```
111
-
112
- You should then be able to run that container by doing something like:
113
-
114
- ```
115
- cd bar
116
- sudo apt-get install runc
117
- sudo runc run id-0001
118
- ```
119
-
120
- ## Supporting non-default architectures
121
-
122
- Docker image repositories can store multiple versions of a single image, with each image corresponding to a different (operating system, cpu architecture, cpu variant) tuple. Occy Strap supports letting you specify which to use with global command line flags. Occy Strap defaults to linux amd64 if you don't specify something different. For example, to fetch the linux arm64 v8 image for busybox, you would run:
123
-
124
- ```
125
- occystrap --os linux --architecture arm64 --variant v8 \
126
- fetch-to-extracted registry-1.docker.io library/busybox \
127
- latest busybox
128
- ```
129
-
130
-
131
-
@@ -1,20 +0,0 @@
1
- occystrap/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- occystrap/common.py,sha256=Zm4hHpn8RgSXp0W86HhZzpyXq19QIsLJgp9SxK_1QQg,1300
3
- occystrap/constants.py,sha256=kmOt-12settGbDTW1efpT3UENRQouG9f0ZjgOqWdrIA,4399
4
- occystrap/docker_extract.py,sha256=j2GSIOShZZh0c5gckXeu-SO7201p4S1EJg3fi7WPQHk,1055
5
- occystrap/docker_registry.py,sha256=C-T1bY1UPJfBZB_Q5gqcZYODikxnrTQU2MfYyoux950,7658
6
- occystrap/main.py,sha256=sevQkqAqgnZRrt61DMUPz9xzoE4AIcWYx2_ZurO_Lls,4059
7
- occystrap/output_directory.py,sha256=S-uL8NSHyHsVKeWsvPRT63E7wE1pWaluGHOFVsS09Xg,11408
8
- occystrap/output_mounts.py,sha256=AH4vouBF9PmhQ5oGfsSZyN1FhatWbs_20IDlI9psn_k,6381
9
- occystrap/output_ocibundle.py,sha256=lsVW66ltL2Y-Mxh5Hp2KSCdh9bvsME1142CJ_Uh99oo,2154
10
- occystrap/output_tarfile.py,sha256=Di8N_2TSo-gQz490D3XOsKCYiv5T_bxZzTRFbd4WGBA,1620
11
- occystrap/util.py,sha256=nrKfAxDUjb_v9ERDXTmMSSNdJSuLgjdtwOMWk46n0a0,2720
12
- occystrap/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- occystrap-0.3.0.dist-info/AUTHORS,sha256=toKLUaf9c-NkNow00B_akwMGcGtm-S_ihcC_eql9qWc,34
14
- occystrap-0.3.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- occystrap-0.3.0.dist-info/METADATA,sha256=PaCk2BSIcly80vIV4LiPlpXoQ9CbYKtb7S5UpaAI5BQ,6308
16
- occystrap-0.3.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
17
- occystrap-0.3.0.dist-info/entry_points.txt,sha256=51kLRjAxFtC6GWCbTFGezSYMNk5t6xrBmS8Pf7gehiU,50
18
- occystrap-0.3.0.dist-info/pbr.json,sha256=2zD1Bsq8TK1jHn5K3MGWkgXT6TxdPUw9MsWVY-Pe89c,46
19
- occystrap-0.3.0.dist-info/top_level.txt,sha256=06nN7FHq2z_Jpp2PZNm3rGOGUA1cIGlUr6MEZrqgOlc,10
20
- occystrap-0.3.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- {"git_version": "27e37dc", "is_release": true}