1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package validate
16
17 import (
18 "bytes"
19 "errors"
20 "fmt"
21 "io"
22 "strings"
23
24 "github.com/google/go-cmp/cmp"
25 v1 "github.com/google/go-containerregistry/pkg/v1"
26 "github.com/google/go-containerregistry/pkg/v1/partial"
27 )
28
29
30 func Image(img v1.Image, opt ...Option) error {
31 errs := []string{}
32 if err := validateLayers(img, opt...); err != nil {
33 errs = append(errs, fmt.Sprintf("validating layers: %v", err))
34 }
35
36 if err := validateConfig(img); err != nil {
37 errs = append(errs, fmt.Sprintf("validating config: %v", err))
38 }
39
40 if err := validateManifest(img); err != nil {
41 errs = append(errs, fmt.Sprintf("validating manifest: %v", err))
42 }
43
44 if len(errs) != 0 {
45 return errors.New(strings.Join(errs, "\n\n"))
46 }
47 return nil
48 }
49
50 func validateConfig(img v1.Image) error {
51 cn, err := img.ConfigName()
52 if err != nil {
53 return err
54 }
55
56 rc, err := img.RawConfigFile()
57 if err != nil {
58 return err
59 }
60
61 hash, size, err := v1.SHA256(bytes.NewReader(rc))
62 if err != nil {
63 return err
64 }
65
66 m, err := img.Manifest()
67 if err != nil {
68 return err
69 }
70
71 cf, err := img.ConfigFile()
72 if err != nil {
73 return err
74 }
75
76 pcf, err := v1.ParseConfigFile(bytes.NewReader(rc))
77 if err != nil {
78 return err
79 }
80
81 errs := []string{}
82 if cn != hash {
83 errs = append(errs, fmt.Sprintf("mismatched config digest: ConfigName()=%s, SHA256(RawConfigFile())=%s", cn, hash))
84 }
85
86 if want, got := m.Config.Size, size; want != got {
87 errs = append(errs, fmt.Sprintf("mismatched config size: Manifest.Config.Size()=%d, len(RawConfigFile())=%d", want, got))
88 }
89
90 if diff := cmp.Diff(pcf, cf); diff != "" {
91 errs = append(errs, fmt.Sprintf("mismatched config content: (-ParseConfigFile(RawConfigFile()) +ConfigFile()) %s", diff))
92 }
93
94 if cf.RootFS.Type != "layers" {
95 errs = append(errs, fmt.Sprintf("invalid ConfigFile.RootFS.Type: %q != %q", cf.RootFS.Type, "layers"))
96 }
97
98 if len(errs) != 0 {
99 return errors.New(strings.Join(errs, "\n"))
100 }
101
102 return nil
103 }
104
105 func validateLayers(img v1.Image, opt ...Option) error {
106 o := makeOptions(opt...)
107
108 layers, err := img.Layers()
109 if err != nil {
110 return err
111 }
112
113 if o.fast {
114 return layersExist(layers)
115 }
116
117 digests := []v1.Hash{}
118 diffids := []v1.Hash{}
119 udiffids := []v1.Hash{}
120 sizes := []int64{}
121 for i, layer := range layers {
122 cl, err := computeLayer(layer)
123 if errors.Is(err, io.ErrUnexpectedEOF) {
124
125
126
127 m, err := img.Manifest()
128 if err != nil {
129 return fmt.Errorf("undersized layer[%d] content", i)
130 }
131 return fmt.Errorf("undersized layer[%d] content: Manifest.Layers[%d].Size=%d", i, i, m.Layers[i].Size)
132 }
133 if err != nil {
134 return err
135 }
136
137
138 digests = append(digests, cl.digest)
139 diffids = append(diffids, cl.diffid)
140 udiffids = append(udiffids, cl.uncompressedDiffid)
141 sizes = append(sizes, cl.size)
142 }
143
144 cf, err := img.ConfigFile()
145 if err != nil {
146 return err
147 }
148
149 m, err := img.Manifest()
150 if err != nil {
151 return err
152 }
153
154 errs := []string{}
155 for i, layer := range layers {
156 digest, err := layer.Digest()
157 if err != nil {
158 return err
159 }
160 diffid, err := layer.DiffID()
161 if err != nil {
162 return err
163 }
164 size, err := layer.Size()
165 if err != nil {
166 return err
167 }
168 mediaType, err := layer.MediaType()
169 if err != nil {
170 return err
171 }
172
173 if _, err := img.LayerByDigest(digest); err != nil {
174 return err
175 }
176
177 if _, err := img.LayerByDiffID(diffid); err != nil {
178 return err
179 }
180
181 if digest != digests[i] {
182 errs = append(errs, fmt.Sprintf("mismatched layer[%d] digest: Digest()=%s, SHA256(Compressed())=%s", i, digest, digests[i]))
183 }
184
185 if m.Layers[i].Digest != digests[i] {
186 errs = append(errs, fmt.Sprintf("mismatched layer[%d] digest: Manifest.Layers[%d].Digest=%s, SHA256(Compressed())=%s", i, i, m.Layers[i].Digest, digests[i]))
187 }
188
189 if diffid != diffids[i] {
190 errs = append(errs, fmt.Sprintf("mismatched layer[%d] diffid: DiffID()=%s, SHA256(Gunzip(Compressed()))=%s", i, diffid, diffids[i]))
191 }
192
193 if diffid != udiffids[i] {
194 errs = append(errs, fmt.Sprintf("mismatched layer[%d] diffid: DiffID()=%s, SHA256(Uncompressed())=%s", i, diffid, udiffids[i]))
195 }
196
197 if cf.RootFS.DiffIDs[i] != diffids[i] {
198 errs = append(errs, fmt.Sprintf("mismatched layer[%d] diffid: ConfigFile.RootFS.DiffIDs[%d]=%s, SHA256(Gunzip(Compressed()))=%s", i, i, cf.RootFS.DiffIDs[i], diffids[i]))
199 }
200
201 if size != sizes[i] {
202 errs = append(errs, fmt.Sprintf("mismatched layer[%d] size: Size()=%d, len(Compressed())=%d", i, size, sizes[i]))
203 }
204
205 if m.Layers[i].Size != sizes[i] {
206 errs = append(errs, fmt.Sprintf("mismatched layer[%d] size: Manifest.Layers[%d].Size=%d, len(Compressed())=%d", i, i, m.Layers[i].Size, sizes[i]))
207 }
208
209 if m.Layers[i].MediaType != mediaType {
210 errs = append(errs, fmt.Sprintf("mismatched layer[%d] mediaType: Manifest.Layers[%d].MediaType=%s, layer.MediaType()=%s", i, i, m.Layers[i].MediaType, mediaType))
211 }
212 }
213 if len(errs) != 0 {
214 return errors.New(strings.Join(errs, "\n"))
215 }
216
217 return nil
218 }
219
220 func validateManifest(img v1.Image) error {
221 digest, err := img.Digest()
222 if err != nil {
223 return err
224 }
225
226 size, err := img.Size()
227 if err != nil {
228 return err
229 }
230
231 rm, err := img.RawManifest()
232 if err != nil {
233 return err
234 }
235
236 hash, _, err := v1.SHA256(bytes.NewReader(rm))
237 if err != nil {
238 return err
239 }
240
241 m, err := img.Manifest()
242 if err != nil {
243 return err
244 }
245
246 pm, err := v1.ParseManifest(bytes.NewReader(rm))
247 if err != nil {
248 return err
249 }
250
251 errs := []string{}
252 if digest != hash {
253 errs = append(errs, fmt.Sprintf("mismatched manifest digest: Digest()=%s, SHA256(RawManifest())=%s", digest, hash))
254 }
255
256 if diff := cmp.Diff(pm, m); diff != "" {
257 errs = append(errs, fmt.Sprintf("mismatched manifest content: (-ParseManifest(RawManifest()) +Manifest()) %s", diff))
258 }
259
260 if size != int64(len(rm)) {
261 errs = append(errs, fmt.Sprintf("mismatched manifest size: Size()=%d, len(RawManifest())=%d", size, len(rm)))
262 }
263
264 if len(errs) != 0 {
265 return errors.New(strings.Join(errs, "\n"))
266 }
267
268 return nil
269 }
270
271 func layersExist(layers []v1.Layer) error {
272 errs := []string{}
273 for _, layer := range layers {
274 ok, err := partial.Exists(layer)
275 if err != nil {
276 errs = append(errs, err.Error())
277 }
278 if !ok {
279 errs = append(errs, "layer does not exist")
280 }
281 }
282
283 if len(errs) != 0 {
284 return errors.New(strings.Join(errs, "\n"))
285 }
286
287 return nil
288 }
289
View as plain text