Browse code

Added Signatures field to Image object

Which is an array of signatures with mostly opaque data. There are
two mandatory fields of each signature:

- Type - the only supported value for now is "atomic"
- Content - opaque binary string

There's one known signature type "atomic" with two mandatory
and one optional attributes:

- ImageHash - hash of a signed image
- ImageIdentity - signed claim representing an origin of the image
- Created - optional; the time of signarure's creation

Following attributes are added for future signing extensions:

- SignedClaims - a map of interesting signed claims that maybe added
later to the signature format but not that important
to become first-class fields
- IssuedBy - issuer of signing certificate if any
- IssuedTo - subject of signing certificate or an image signer

Signed-off-by: Michal Minar <miminar@redhat.com>

Michal Minar authored on 2016/06/07 00:59:56
Showing 12 changed files
... ...
@@ -21059,6 +21059,13 @@
21059 21059
        "$ref": "v1.ImageLayer"
21060 21060
       },
21061 21061
       "description": "DockerImageLayers represents the layers in the image. May not be set if the image does not define that data."
21062
+     },
21063
+     "signatures": {
21064
+      "type": "array",
21065
+      "items": {
21066
+       "$ref": "v1.ImageSignature"
21067
+      },
21068
+      "description": "Signatures holds all signatures of the image."
21062 21069
      }
21063 21070
     }
21064 21071
    },
... ...
@@ -21081,6 +21088,123 @@
21081 21081
      }
21082 21082
     }
21083 21083
    },
21084
+   "v1.ImageSignature": {
21085
+    "id": "v1.ImageSignature",
21086
+    "description": "ImageSignature holds a signature of an image. It allows to verify image identity and possibly other claims as long as the signature is trusted. Based on this information it is possible to restrict runnable images to those matching cluster-wide policy. There are two mandatory fields provided by client: Type and Content. They should be parsed by clients doing image verification. The others are parsed from signature's content by the server. They serve just an informative purpose.",
21087
+    "required": [
21088
+     "type",
21089
+     "content"
21090
+    ],
21091
+    "properties": {
21092
+     "type": {
21093
+      "type": "string",
21094
+      "description": "Required: Describes a type of stored blob."
21095
+     },
21096
+     "content": {
21097
+      "type": "array",
21098
+      "items": {
21099
+       "$ref": "integer"
21100
+      },
21101
+      "description": "Required: An opaque binary string which is an image's signature."
21102
+     },
21103
+     "conditions": {
21104
+      "type": "array",
21105
+      "items": {
21106
+       "$ref": "v1.SignatureCondition"
21107
+      },
21108
+      "description": "Conditions represent the latest available observations of a signature's current state."
21109
+     },
21110
+     "imageIdentity": {
21111
+      "type": "string",
21112
+      "description": "A human readable string representing image's identity. It could be a product name and version, or an image pull spec (e.g. \"registry.access.redhat.com/rhel7/rhel:7.2\")."
21113
+     },
21114
+     "signedClaims": {
21115
+      "type": "any",
21116
+      "description": "Contains claims from the signature."
21117
+     },
21118
+     "created": {
21119
+      "type": "string",
21120
+      "description": "If specified, it is the time of signature's creation."
21121
+     },
21122
+     "issuedBy": {
21123
+      "$ref": "v1.SignatureIssuer",
21124
+      "description": "If specified, it holds information about an issuer of signing certificate or key (a person or entity who signed the signing certificate or key)."
21125
+     },
21126
+     "issuedTo": {
21127
+      "$ref": "v1.SignatureSubject",
21128
+      "description": "If specified, it holds information about a subject of signing certificate or key (a person or entity who signed the image)."
21129
+     }
21130
+    }
21131
+   },
21132
+   "v1.SignatureCondition": {
21133
+    "id": "v1.SignatureCondition",
21134
+    "description": "SignatureCondition describes an image signature condition of particular kind at particular probe time.",
21135
+    "required": [
21136
+     "type",
21137
+     "status"
21138
+    ],
21139
+    "properties": {
21140
+     "type": {
21141
+      "type": "string",
21142
+      "description": "Type of job condition, Complete or Failed."
21143
+     },
21144
+     "status": {
21145
+      "type": "string",
21146
+      "description": "Status of the condition, one of True, False, Unknown."
21147
+     },
21148
+     "lastProbeTime": {
21149
+      "type": "string",
21150
+      "description": "Last time the condition was checked."
21151
+     },
21152
+     "lastTransitionTime": {
21153
+      "type": "string",
21154
+      "description": "Last time the condition transit from one status to another."
21155
+     },
21156
+     "reason": {
21157
+      "type": "string",
21158
+      "description": "(brief) reason for the condition's last transition."
21159
+     },
21160
+     "message": {
21161
+      "type": "string",
21162
+      "description": "Human readable message indicating details about last transition."
21163
+     }
21164
+    }
21165
+   },
21166
+   "v1.SignatureIssuer": {
21167
+    "id": "v1.SignatureIssuer",
21168
+    "description": "SignatureIssuer holds information about an issuer of signing certificate or key.",
21169
+    "properties": {
21170
+     "organization": {
21171
+      "type": "string",
21172
+      "description": "Organization name."
21173
+     },
21174
+     "commonName": {
21175
+      "type": "string",
21176
+      "description": "Common name (e.g. openshift-signing-service)."
21177
+     }
21178
+    }
21179
+   },
21180
+   "v1.SignatureSubject": {
21181
+    "id": "v1.SignatureSubject",
21182
+    "description": "SignatureSubject holds information about a person or entity who created the signature.",
21183
+    "required": [
21184
+     "publicKeyID"
21185
+    ],
21186
+    "properties": {
21187
+     "organization": {
21188
+      "type": "string",
21189
+      "description": "Organization name."
21190
+     },
21191
+     "commonName": {
21192
+      "type": "string",
21193
+      "description": "Common name (e.g. openshift-signing-service)."
21194
+     },
21195
+     "publicKeyID": {
21196
+      "type": "string",
21197
+      "description": "If present, it is a human readable key id of public key belonging to the subject used to verify image signature. It should contain at least 64 lowest bits of public key's fingerprint (e.g. 0x685ebe62bf278440)."
21198
+     }
21199
+    }
21200
+   },
21084 21201
    "v1.ImageStreamImage": {
21085 21202
     "id": "v1.ImageStreamImage",
21086 21203
     "description": "ImageStreamImage represents an Image that is retrieved by image name from an ImageStream.",
... ...
@@ -26,6 +26,7 @@ func init() {
26 26
 		DeepCopy_api_ImageImportStatus,
27 27
 		DeepCopy_api_ImageLayer,
28 28
 		DeepCopy_api_ImageList,
29
+		DeepCopy_api_ImageSignature,
29 30
 		DeepCopy_api_ImageStream,
30 31
 		DeepCopy_api_ImageStreamImage,
31 32
 		DeepCopy_api_ImageStreamImport,
... ...
@@ -39,6 +40,10 @@ func init() {
39 39
 		DeepCopy_api_ImageStreamTagList,
40 40
 		DeepCopy_api_RepositoryImportSpec,
41 41
 		DeepCopy_api_RepositoryImportStatus,
42
+		DeepCopy_api_SignatureCondition,
43
+		DeepCopy_api_SignatureGenericEntity,
44
+		DeepCopy_api_SignatureIssuer,
45
+		DeepCopy_api_SignatureSubject,
42 46
 		DeepCopy_api_TagEvent,
43 47
 		DeepCopy_api_TagEventCondition,
44 48
 		DeepCopy_api_TagEventList,
... ...
@@ -312,6 +317,17 @@ func DeepCopy_api_Image(in Image, out *Image, c *conversion.Cloner) error {
312 312
 	} else {
313 313
 		out.DockerImageLayers = nil
314 314
 	}
315
+	if in.Signatures != nil {
316
+		in, out := in.Signatures, &out.Signatures
317
+		*out = make([]ImageSignature, len(in))
318
+		for i := range in {
319
+			if err := DeepCopy_api_ImageSignature(in[i], &(*out)[i], c); err != nil {
320
+				return err
321
+			}
322
+		}
323
+	} else {
324
+		out.Signatures = nil
325
+	}
315 326
 	return nil
316 327
 }
317 328
 
... ...
@@ -379,6 +395,66 @@ func DeepCopy_api_ImageList(in ImageList, out *ImageList, c *conversion.Cloner)
379 379
 	return nil
380 380
 }
381 381
 
382
+func DeepCopy_api_ImageSignature(in ImageSignature, out *ImageSignature, c *conversion.Cloner) error {
383
+	out.Type = in.Type
384
+	if in.Content != nil {
385
+		in, out := in.Content, &out.Content
386
+		*out = make([]byte, len(in))
387
+		copy(*out, in)
388
+	} else {
389
+		out.Content = nil
390
+	}
391
+	if in.Conditions != nil {
392
+		in, out := in.Conditions, &out.Conditions
393
+		*out = make([]SignatureCondition, len(in))
394
+		for i := range in {
395
+			if err := DeepCopy_api_SignatureCondition(in[i], &(*out)[i], c); err != nil {
396
+				return err
397
+			}
398
+		}
399
+	} else {
400
+		out.Conditions = nil
401
+	}
402
+	out.ImageIdentity = in.ImageIdentity
403
+	if in.SignedClaims != nil {
404
+		in, out := in.SignedClaims, &out.SignedClaims
405
+		*out = make(map[string]string)
406
+		for key, val := range in {
407
+			(*out)[key] = val
408
+		}
409
+	} else {
410
+		out.SignedClaims = nil
411
+	}
412
+	if in.Created != nil {
413
+		in, out := in.Created, &out.Created
414
+		*out = new(unversioned.Time)
415
+		if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil {
416
+			return err
417
+		}
418
+	} else {
419
+		out.Created = nil
420
+	}
421
+	if in.IssuedBy != nil {
422
+		in, out := in.IssuedBy, &out.IssuedBy
423
+		*out = new(SignatureIssuer)
424
+		if err := DeepCopy_api_SignatureIssuer(*in, *out, c); err != nil {
425
+			return err
426
+		}
427
+	} else {
428
+		out.IssuedBy = nil
429
+	}
430
+	if in.IssuedTo != nil {
431
+		in, out := in.IssuedTo, &out.IssuedTo
432
+		*out = new(SignatureSubject)
433
+		if err := DeepCopy_api_SignatureSubject(*in, *out, c); err != nil {
434
+			return err
435
+		}
436
+	} else {
437
+		out.IssuedTo = nil
438
+	}
439
+	return nil
440
+}
441
+
382 442
 func DeepCopy_api_ImageStream(in ImageStream, out *ImageStream, c *conversion.Cloner) error {
383 443
 	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
384 444
 		return err
... ...
@@ -645,6 +721,41 @@ func DeepCopy_api_RepositoryImportStatus(in RepositoryImportStatus, out *Reposit
645 645
 	return nil
646 646
 }
647 647
 
648
+func DeepCopy_api_SignatureCondition(in SignatureCondition, out *SignatureCondition, c *conversion.Cloner) error {
649
+	out.Type = in.Type
650
+	out.Status = in.Status
651
+	if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil {
652
+		return err
653
+	}
654
+	if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil {
655
+		return err
656
+	}
657
+	out.Reason = in.Reason
658
+	out.Message = in.Message
659
+	return nil
660
+}
661
+
662
+func DeepCopy_api_SignatureGenericEntity(in SignatureGenericEntity, out *SignatureGenericEntity, c *conversion.Cloner) error {
663
+	out.Organization = in.Organization
664
+	out.CommonName = in.CommonName
665
+	return nil
666
+}
667
+
668
+func DeepCopy_api_SignatureIssuer(in SignatureIssuer, out *SignatureIssuer, c *conversion.Cloner) error {
669
+	if err := DeepCopy_api_SignatureGenericEntity(in.SignatureGenericEntity, &out.SignatureGenericEntity, c); err != nil {
670
+		return err
671
+	}
672
+	return nil
673
+}
674
+
675
+func DeepCopy_api_SignatureSubject(in SignatureSubject, out *SignatureSubject, c *conversion.Cloner) error {
676
+	if err := DeepCopy_api_SignatureGenericEntity(in.SignatureGenericEntity, &out.SignatureGenericEntity, c); err != nil {
677
+		return err
678
+	}
679
+	out.PublicKeyID = in.PublicKeyID
680
+	return nil
681
+}
682
+
648 683
 func DeepCopy_api_TagEvent(in TagEvent, out *TagEvent, c *conversion.Cloner) error {
649 684
 	if err := unversioned.DeepCopy_unversioned_Time(in.Created, &out.Created, c); err != nil {
650 685
 		return err
... ...
@@ -66,6 +66,8 @@ type Image struct {
66 66
 	DockerImageManifest string
67 67
 	// DockerImageLayers represents the layers in the image. May not be set if the image does not define that data.
68 68
 	DockerImageLayers []ImageLayer
69
+	// Signatures holds all signatures of the image.
70
+	Signatures []ImageSignature
69 71
 }
70 72
 
71 73
 // ImageLayer represents a single layer of the image. Some images may have multiple layers. Some may have none.
... ...
@@ -76,6 +78,99 @@ type ImageLayer struct {
76 76
 	Size int64
77 77
 }
78 78
 
79
+const (
80
+	// The supported type of image signature.
81
+	ImageSignatureTypeAtomicImageV1 string = "AtomicImageV1"
82
+)
83
+
84
+// ImageSignature holds a signature of an image. It allows to verify image identity and possibly other claims
85
+// as long as the signature is trusted. Based on this information it is possible to restrict runnable images
86
+// to those matching cluster-wide policy.
87
+// There are two mandatory fields provided by client: Type and Content. They should be parsed by clients doing
88
+// image verification. The others are parsed from signature's content by the server. They serve just an
89
+// informative purpose.
90
+type ImageSignature struct {
91
+	// Required: Describes a type of stored blob.
92
+	Type string
93
+	// Required: An opaque binary string which is an image's signature.
94
+	Content []byte
95
+	// Conditions represent the latest available observations of a signature's current state.
96
+	Conditions []SignatureCondition
97
+
98
+	// Following metadata fields will be set by server if the signature content is successfully parsed and
99
+	// the information available.
100
+
101
+	// A human readable string representing image's identity. It could be a product name and version, or an
102
+	// image pull spec (e.g. "registry.access.redhat.com/rhel7/rhel:7.2").
103
+	ImageIdentity string
104
+	// Contains claims from the signature.
105
+	SignedClaims map[string]string
106
+	// If specified, it is the time of signature's creation.
107
+	Created *unversioned.Time
108
+	// If specified, it holds information about an issuer of signing certificate or key (a person or entity
109
+	// who signed the signing certificate or key).
110
+	IssuedBy *SignatureIssuer
111
+	// If specified, it holds information about a subject of signing certificate or key (a person or entity
112
+	// who signed the image).
113
+	IssuedTo *SignatureSubject
114
+}
115
+
116
+// These are valid conditions of an image signature.
117
+const (
118
+	// SignatureTrusted means the signing key or certificate was valid and the signature matched the image at
119
+	// the probe time.
120
+	SignatureTrusted = "Trusted"
121
+	// SignatureForImage means the signature matches image object containing it.
122
+	SignatureForImage = "ForImage"
123
+	// SignatureExpired means the signature or its signing key or certificate had been expired at the probe
124
+	// time.
125
+	SignatureExpired = "Expired"
126
+	// SignatureRevoked means the signature or its signing key or certificate has been revoked.
127
+	SignatureRevoked = "Revoked"
128
+)
129
+
130
+/// SignatureConditionType is a type of image signature condition.
131
+type SignatureConditionType string
132
+
133
+// SignatureCondition describes an image signature condition of particular kind at particular probe time.
134
+type SignatureCondition struct {
135
+	// Type of job condition, Complete or Failed.
136
+	Type SignatureConditionType
137
+	// Status of the condition, one of True, False, Unknown.
138
+	Status kapi.ConditionStatus
139
+	// Last time the condition was checked.
140
+	LastProbeTime unversioned.Time
141
+	// Last time the condition transit from one status to another.
142
+	LastTransitionTime unversioned.Time
143
+	// (brief) reason for the condition's last transition.
144
+	Reason string
145
+	// Human readable message indicating details about last transition.
146
+	Message string
147
+}
148
+
149
+// SignatureGenericEntity holds a generic information about a person or entity who is an issuer or a subject
150
+// of signing certificate or key.
151
+type SignatureGenericEntity struct {
152
+	// Organization name.
153
+	Organization string
154
+	// Common name (e.g. openshift-signing-service).
155
+	CommonName string
156
+}
157
+
158
+// SignatureIssuer holds information about an issuer of signing certificate or key.
159
+type SignatureIssuer struct {
160
+	SignatureGenericEntity
161
+}
162
+
163
+// SignatureSubject holds information about a person or entity who created the signature.
164
+type SignatureSubject struct {
165
+	SignatureGenericEntity
166
+	// If present, it is a human readable key id of public key belonging to the subject used to verify image
167
+	// signature. It should contain at least 64 lowest bits of public key's fingerprint (e.g.
168
+	// 0x685ebe62bf278440).
169
+	PublicKeyID string
170
+}
171
+
79 172
 // ImageStreamList is a list of ImageStream objects.
80 173
 type ImageStreamList struct {
81 174
 	unversioned.TypeMeta
... ...
@@ -52,6 +52,17 @@ func Convert_api_Image_To_v1_Image(in *newer.Image, out *Image, s conversion.Sco
52 52
 		out.DockerImageLayers = nil
53 53
 	}
54 54
 
55
+	if in.Signatures != nil {
56
+		out.Signatures = make([]ImageSignature, len(in.Signatures))
57
+		for i := range in.Signatures {
58
+			if err := s.Convert(&in.Signatures[i], &out.Signatures[i], 0); err != nil {
59
+				return err
60
+			}
61
+		}
62
+	} else {
63
+		out.Signatures = nil
64
+	}
65
+
55 66
 	return nil
56 67
 }
57 68
 
... ...
@@ -92,6 +103,17 @@ func Convert_v1_Image_To_api_Image(in *Image, out *newer.Image, s conversion.Sco
92 92
 		out.DockerImageLayers = nil
93 93
 	}
94 94
 
95
+	if in.Signatures != nil {
96
+		out.Signatures = make([]newer.ImageSignature, len(in.Signatures))
97
+		for i := range in.Signatures {
98
+			if err := s.Convert(&in.Signatures[i], &out.Signatures[i], 0); err != nil {
99
+				return err
100
+			}
101
+		}
102
+	} else {
103
+		out.Signatures = nil
104
+	}
105
+
95 106
 	return nil
96 107
 }
97 108
 
... ...
@@ -7,6 +7,7 @@ package v1
7 7
 import (
8 8
 	image_api "github.com/openshift/origin/pkg/image/api"
9 9
 	api "k8s.io/kubernetes/pkg/api"
10
+	unversioned "k8s.io/kubernetes/pkg/api/unversioned"
10 11
 	api_v1 "k8s.io/kubernetes/pkg/api/v1"
11 12
 	conversion "k8s.io/kubernetes/pkg/conversion"
12 13
 	reflect "reflect"
... ...
@@ -26,6 +27,8 @@ func init() {
26 26
 		Convert_api_ImageLayer_To_v1_ImageLayer,
27 27
 		Convert_v1_ImageList_To_api_ImageList,
28 28
 		Convert_api_ImageList_To_v1_ImageList,
29
+		Convert_v1_ImageSignature_To_api_ImageSignature,
30
+		Convert_api_ImageSignature_To_v1_ImageSignature,
29 31
 		Convert_v1_ImageStream_To_api_ImageStream,
30 32
 		Convert_api_ImageStream_To_v1_ImageStream,
31 33
 		Convert_v1_ImageStreamImage_To_api_ImageStreamImage,
... ...
@@ -52,6 +55,14 @@ func init() {
52 52
 		Convert_api_RepositoryImportSpec_To_v1_RepositoryImportSpec,
53 53
 		Convert_v1_RepositoryImportStatus_To_api_RepositoryImportStatus,
54 54
 		Convert_api_RepositoryImportStatus_To_v1_RepositoryImportStatus,
55
+		Convert_v1_SignatureCondition_To_api_SignatureCondition,
56
+		Convert_api_SignatureCondition_To_v1_SignatureCondition,
57
+		Convert_v1_SignatureGenericEntity_To_api_SignatureGenericEntity,
58
+		Convert_api_SignatureGenericEntity_To_v1_SignatureGenericEntity,
59
+		Convert_v1_SignatureIssuer_To_api_SignatureIssuer,
60
+		Convert_api_SignatureIssuer_To_v1_SignatureIssuer,
61
+		Convert_v1_SignatureSubject_To_api_SignatureSubject,
62
+		Convert_api_SignatureSubject_To_v1_SignatureSubject,
55 63
 		Convert_v1_TagEvent_To_api_TagEvent,
56 64
 		Convert_api_TagEvent_To_v1_TagEvent,
57 65
 		Convert_v1_TagEventCondition_To_api_TagEventCondition,
... ...
@@ -288,6 +299,132 @@ func Convert_api_ImageList_To_v1_ImageList(in *image_api.ImageList, out *ImageLi
288 288
 	return autoConvert_api_ImageList_To_v1_ImageList(in, out, s)
289 289
 }
290 290
 
291
+func autoConvert_v1_ImageSignature_To_api_ImageSignature(in *ImageSignature, out *image_api.ImageSignature, s conversion.Scope) error {
292
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
293
+		defaulting.(func(*ImageSignature))(in)
294
+	}
295
+	out.Type = in.Type
296
+	if err := conversion.Convert_Slice_byte_To_Slice_byte(&in.Content, &out.Content, s); err != nil {
297
+		return err
298
+	}
299
+	if in.Conditions != nil {
300
+		in, out := &in.Conditions, &out.Conditions
301
+		*out = make([]image_api.SignatureCondition, len(*in))
302
+		for i := range *in {
303
+			if err := Convert_v1_SignatureCondition_To_api_SignatureCondition(&(*in)[i], &(*out)[i], s); err != nil {
304
+				return err
305
+			}
306
+		}
307
+	} else {
308
+		out.Conditions = nil
309
+	}
310
+	out.ImageIdentity = in.ImageIdentity
311
+	if in.SignedClaims != nil {
312
+		in, out := &in.SignedClaims, &out.SignedClaims
313
+		*out = make(map[string]string, len(*in))
314
+		for key, val := range *in {
315
+			(*out)[key] = val
316
+		}
317
+	} else {
318
+		out.SignedClaims = nil
319
+	}
320
+	if in.Created != nil {
321
+		in, out := &in.Created, &out.Created
322
+		*out = new(unversioned.Time)
323
+		if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil {
324
+			return err
325
+		}
326
+	} else {
327
+		out.Created = nil
328
+	}
329
+	if in.IssuedBy != nil {
330
+		in, out := &in.IssuedBy, &out.IssuedBy
331
+		*out = new(image_api.SignatureIssuer)
332
+		if err := Convert_v1_SignatureIssuer_To_api_SignatureIssuer(*in, *out, s); err != nil {
333
+			return err
334
+		}
335
+	} else {
336
+		out.IssuedBy = nil
337
+	}
338
+	if in.IssuedTo != nil {
339
+		in, out := &in.IssuedTo, &out.IssuedTo
340
+		*out = new(image_api.SignatureSubject)
341
+		if err := Convert_v1_SignatureSubject_To_api_SignatureSubject(*in, *out, s); err != nil {
342
+			return err
343
+		}
344
+	} else {
345
+		out.IssuedTo = nil
346
+	}
347
+	return nil
348
+}
349
+
350
+func Convert_v1_ImageSignature_To_api_ImageSignature(in *ImageSignature, out *image_api.ImageSignature, s conversion.Scope) error {
351
+	return autoConvert_v1_ImageSignature_To_api_ImageSignature(in, out, s)
352
+}
353
+
354
+func autoConvert_api_ImageSignature_To_v1_ImageSignature(in *image_api.ImageSignature, out *ImageSignature, s conversion.Scope) error {
355
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
356
+		defaulting.(func(*image_api.ImageSignature))(in)
357
+	}
358
+	out.Type = in.Type
359
+	if err := conversion.Convert_Slice_byte_To_Slice_byte(&in.Content, &out.Content, s); err != nil {
360
+		return err
361
+	}
362
+	if in.Conditions != nil {
363
+		in, out := &in.Conditions, &out.Conditions
364
+		*out = make([]SignatureCondition, len(*in))
365
+		for i := range *in {
366
+			if err := Convert_api_SignatureCondition_To_v1_SignatureCondition(&(*in)[i], &(*out)[i], s); err != nil {
367
+				return err
368
+			}
369
+		}
370
+	} else {
371
+		out.Conditions = nil
372
+	}
373
+	out.ImageIdentity = in.ImageIdentity
374
+	if in.SignedClaims != nil {
375
+		in, out := &in.SignedClaims, &out.SignedClaims
376
+		*out = make(map[string]string, len(*in))
377
+		for key, val := range *in {
378
+			(*out)[key] = val
379
+		}
380
+	} else {
381
+		out.SignedClaims = nil
382
+	}
383
+	if in.Created != nil {
384
+		in, out := &in.Created, &out.Created
385
+		*out = new(unversioned.Time)
386
+		if err := api.Convert_unversioned_Time_To_unversioned_Time(*in, *out, s); err != nil {
387
+			return err
388
+		}
389
+	} else {
390
+		out.Created = nil
391
+	}
392
+	if in.IssuedBy != nil {
393
+		in, out := &in.IssuedBy, &out.IssuedBy
394
+		*out = new(SignatureIssuer)
395
+		if err := Convert_api_SignatureIssuer_To_v1_SignatureIssuer(*in, *out, s); err != nil {
396
+			return err
397
+		}
398
+	} else {
399
+		out.IssuedBy = nil
400
+	}
401
+	if in.IssuedTo != nil {
402
+		in, out := &in.IssuedTo, &out.IssuedTo
403
+		*out = new(SignatureSubject)
404
+		if err := Convert_api_SignatureSubject_To_v1_SignatureSubject(*in, *out, s); err != nil {
405
+			return err
406
+		}
407
+	} else {
408
+		out.IssuedTo = nil
409
+	}
410
+	return nil
411
+}
412
+
413
+func Convert_api_ImageSignature_To_v1_ImageSignature(in *image_api.ImageSignature, out *ImageSignature, s conversion.Scope) error {
414
+	return autoConvert_api_ImageSignature_To_v1_ImageSignature(in, out, s)
415
+}
416
+
291 417
 func autoConvert_v1_ImageStream_To_api_ImageStream(in *ImageStream, out *image_api.ImageStream, s conversion.Scope) error {
292 418
 	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
293 419
 		defaulting.(func(*ImageStream))(in)
... ...
@@ -888,6 +1025,132 @@ func Convert_api_RepositoryImportStatus_To_v1_RepositoryImportStatus(in *image_a
888 888
 	return autoConvert_api_RepositoryImportStatus_To_v1_RepositoryImportStatus(in, out, s)
889 889
 }
890 890
 
891
+func autoConvert_v1_SignatureCondition_To_api_SignatureCondition(in *SignatureCondition, out *image_api.SignatureCondition, s conversion.Scope) error {
892
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
893
+		defaulting.(func(*SignatureCondition))(in)
894
+	}
895
+	out.Type = image_api.SignatureConditionType(in.Type)
896
+	out.Status = api.ConditionStatus(in.Status)
897
+	if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastProbeTime, &out.LastProbeTime, s); err != nil {
898
+		return err
899
+	}
900
+	if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil {
901
+		return err
902
+	}
903
+	out.Reason = in.Reason
904
+	out.Message = in.Message
905
+	return nil
906
+}
907
+
908
+func Convert_v1_SignatureCondition_To_api_SignatureCondition(in *SignatureCondition, out *image_api.SignatureCondition, s conversion.Scope) error {
909
+	return autoConvert_v1_SignatureCondition_To_api_SignatureCondition(in, out, s)
910
+}
911
+
912
+func autoConvert_api_SignatureCondition_To_v1_SignatureCondition(in *image_api.SignatureCondition, out *SignatureCondition, s conversion.Scope) error {
913
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
914
+		defaulting.(func(*image_api.SignatureCondition))(in)
915
+	}
916
+	out.Type = SignatureConditionType(in.Type)
917
+	out.Status = api_v1.ConditionStatus(in.Status)
918
+	if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastProbeTime, &out.LastProbeTime, s); err != nil {
919
+		return err
920
+	}
921
+	if err := api.Convert_unversioned_Time_To_unversioned_Time(&in.LastTransitionTime, &out.LastTransitionTime, s); err != nil {
922
+		return err
923
+	}
924
+	out.Reason = in.Reason
925
+	out.Message = in.Message
926
+	return nil
927
+}
928
+
929
+func Convert_api_SignatureCondition_To_v1_SignatureCondition(in *image_api.SignatureCondition, out *SignatureCondition, s conversion.Scope) error {
930
+	return autoConvert_api_SignatureCondition_To_v1_SignatureCondition(in, out, s)
931
+}
932
+
933
+func autoConvert_v1_SignatureGenericEntity_To_api_SignatureGenericEntity(in *SignatureGenericEntity, out *image_api.SignatureGenericEntity, s conversion.Scope) error {
934
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
935
+		defaulting.(func(*SignatureGenericEntity))(in)
936
+	}
937
+	out.Organization = in.Organization
938
+	out.CommonName = in.CommonName
939
+	return nil
940
+}
941
+
942
+func Convert_v1_SignatureGenericEntity_To_api_SignatureGenericEntity(in *SignatureGenericEntity, out *image_api.SignatureGenericEntity, s conversion.Scope) error {
943
+	return autoConvert_v1_SignatureGenericEntity_To_api_SignatureGenericEntity(in, out, s)
944
+}
945
+
946
+func autoConvert_api_SignatureGenericEntity_To_v1_SignatureGenericEntity(in *image_api.SignatureGenericEntity, out *SignatureGenericEntity, s conversion.Scope) error {
947
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
948
+		defaulting.(func(*image_api.SignatureGenericEntity))(in)
949
+	}
950
+	out.Organization = in.Organization
951
+	out.CommonName = in.CommonName
952
+	return nil
953
+}
954
+
955
+func Convert_api_SignatureGenericEntity_To_v1_SignatureGenericEntity(in *image_api.SignatureGenericEntity, out *SignatureGenericEntity, s conversion.Scope) error {
956
+	return autoConvert_api_SignatureGenericEntity_To_v1_SignatureGenericEntity(in, out, s)
957
+}
958
+
959
+func autoConvert_v1_SignatureIssuer_To_api_SignatureIssuer(in *SignatureIssuer, out *image_api.SignatureIssuer, s conversion.Scope) error {
960
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
961
+		defaulting.(func(*SignatureIssuer))(in)
962
+	}
963
+	if err := Convert_v1_SignatureGenericEntity_To_api_SignatureGenericEntity(&in.SignatureGenericEntity, &out.SignatureGenericEntity, s); err != nil {
964
+		return err
965
+	}
966
+	return nil
967
+}
968
+
969
+func Convert_v1_SignatureIssuer_To_api_SignatureIssuer(in *SignatureIssuer, out *image_api.SignatureIssuer, s conversion.Scope) error {
970
+	return autoConvert_v1_SignatureIssuer_To_api_SignatureIssuer(in, out, s)
971
+}
972
+
973
+func autoConvert_api_SignatureIssuer_To_v1_SignatureIssuer(in *image_api.SignatureIssuer, out *SignatureIssuer, s conversion.Scope) error {
974
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
975
+		defaulting.(func(*image_api.SignatureIssuer))(in)
976
+	}
977
+	if err := Convert_api_SignatureGenericEntity_To_v1_SignatureGenericEntity(&in.SignatureGenericEntity, &out.SignatureGenericEntity, s); err != nil {
978
+		return err
979
+	}
980
+	return nil
981
+}
982
+
983
+func Convert_api_SignatureIssuer_To_v1_SignatureIssuer(in *image_api.SignatureIssuer, out *SignatureIssuer, s conversion.Scope) error {
984
+	return autoConvert_api_SignatureIssuer_To_v1_SignatureIssuer(in, out, s)
985
+}
986
+
987
+func autoConvert_v1_SignatureSubject_To_api_SignatureSubject(in *SignatureSubject, out *image_api.SignatureSubject, s conversion.Scope) error {
988
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
989
+		defaulting.(func(*SignatureSubject))(in)
990
+	}
991
+	if err := Convert_v1_SignatureGenericEntity_To_api_SignatureGenericEntity(&in.SignatureGenericEntity, &out.SignatureGenericEntity, s); err != nil {
992
+		return err
993
+	}
994
+	out.PublicKeyID = in.PublicKeyID
995
+	return nil
996
+}
997
+
998
+func Convert_v1_SignatureSubject_To_api_SignatureSubject(in *SignatureSubject, out *image_api.SignatureSubject, s conversion.Scope) error {
999
+	return autoConvert_v1_SignatureSubject_To_api_SignatureSubject(in, out, s)
1000
+}
1001
+
1002
+func autoConvert_api_SignatureSubject_To_v1_SignatureSubject(in *image_api.SignatureSubject, out *SignatureSubject, s conversion.Scope) error {
1003
+	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
1004
+		defaulting.(func(*image_api.SignatureSubject))(in)
1005
+	}
1006
+	if err := Convert_api_SignatureGenericEntity_To_v1_SignatureGenericEntity(&in.SignatureGenericEntity, &out.SignatureGenericEntity, s); err != nil {
1007
+		return err
1008
+	}
1009
+	out.PublicKeyID = in.PublicKeyID
1010
+	return nil
1011
+}
1012
+
1013
+func Convert_api_SignatureSubject_To_v1_SignatureSubject(in *image_api.SignatureSubject, out *SignatureSubject, s conversion.Scope) error {
1014
+	return autoConvert_api_SignatureSubject_To_v1_SignatureSubject(in, out, s)
1015
+}
1016
+
891 1017
 func autoConvert_v1_TagEvent_To_api_TagEvent(in *TagEvent, out *image_api.TagEvent, s conversion.Scope) error {
892 1018
 	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
893 1019
 		defaulting.(func(*TagEvent))(in)
... ...
@@ -20,6 +20,7 @@ func init() {
20 20
 		DeepCopy_v1_ImageImportStatus,
21 21
 		DeepCopy_v1_ImageLayer,
22 22
 		DeepCopy_v1_ImageList,
23
+		DeepCopy_v1_ImageSignature,
23 24
 		DeepCopy_v1_ImageStream,
24 25
 		DeepCopy_v1_ImageStreamImage,
25 26
 		DeepCopy_v1_ImageStreamImport,
... ...
@@ -34,6 +35,10 @@ func init() {
34 34
 		DeepCopy_v1_NamedTagEventList,
35 35
 		DeepCopy_v1_RepositoryImportSpec,
36 36
 		DeepCopy_v1_RepositoryImportStatus,
37
+		DeepCopy_v1_SignatureCondition,
38
+		DeepCopy_v1_SignatureGenericEntity,
39
+		DeepCopy_v1_SignatureIssuer,
40
+		DeepCopy_v1_SignatureSubject,
37 41
 		DeepCopy_v1_TagEvent,
38 42
 		DeepCopy_v1_TagEventCondition,
39 43
 		DeepCopy_v1_TagImportPolicy,
... ...
@@ -77,6 +82,17 @@ func DeepCopy_v1_Image(in Image, out *Image, c *conversion.Cloner) error {
77 77
 	} else {
78 78
 		out.DockerImageLayers = nil
79 79
 	}
80
+	if in.Signatures != nil {
81
+		in, out := in.Signatures, &out.Signatures
82
+		*out = make([]ImageSignature, len(in))
83
+		for i := range in {
84
+			if err := DeepCopy_v1_ImageSignature(in[i], &(*out)[i], c); err != nil {
85
+				return err
86
+			}
87
+		}
88
+	} else {
89
+		out.Signatures = nil
90
+	}
80 91
 	return nil
81 92
 }
82 93
 
... ...
@@ -144,6 +160,66 @@ func DeepCopy_v1_ImageList(in ImageList, out *ImageList, c *conversion.Cloner) e
144 144
 	return nil
145 145
 }
146 146
 
147
+func DeepCopy_v1_ImageSignature(in ImageSignature, out *ImageSignature, c *conversion.Cloner) error {
148
+	out.Type = in.Type
149
+	if in.Content != nil {
150
+		in, out := in.Content, &out.Content
151
+		*out = make([]byte, len(in))
152
+		copy(*out, in)
153
+	} else {
154
+		out.Content = nil
155
+	}
156
+	if in.Conditions != nil {
157
+		in, out := in.Conditions, &out.Conditions
158
+		*out = make([]SignatureCondition, len(in))
159
+		for i := range in {
160
+			if err := DeepCopy_v1_SignatureCondition(in[i], &(*out)[i], c); err != nil {
161
+				return err
162
+			}
163
+		}
164
+	} else {
165
+		out.Conditions = nil
166
+	}
167
+	out.ImageIdentity = in.ImageIdentity
168
+	if in.SignedClaims != nil {
169
+		in, out := in.SignedClaims, &out.SignedClaims
170
+		*out = make(map[string]string)
171
+		for key, val := range in {
172
+			(*out)[key] = val
173
+		}
174
+	} else {
175
+		out.SignedClaims = nil
176
+	}
177
+	if in.Created != nil {
178
+		in, out := in.Created, &out.Created
179
+		*out = new(unversioned.Time)
180
+		if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil {
181
+			return err
182
+		}
183
+	} else {
184
+		out.Created = nil
185
+	}
186
+	if in.IssuedBy != nil {
187
+		in, out := in.IssuedBy, &out.IssuedBy
188
+		*out = new(SignatureIssuer)
189
+		if err := DeepCopy_v1_SignatureIssuer(*in, *out, c); err != nil {
190
+			return err
191
+		}
192
+	} else {
193
+		out.IssuedBy = nil
194
+	}
195
+	if in.IssuedTo != nil {
196
+		in, out := in.IssuedTo, &out.IssuedTo
197
+		*out = new(SignatureSubject)
198
+		if err := DeepCopy_v1_SignatureSubject(*in, *out, c); err != nil {
199
+			return err
200
+		}
201
+	} else {
202
+		out.IssuedTo = nil
203
+	}
204
+	return nil
205
+}
206
+
147 207
 func DeepCopy_v1_ImageStream(in ImageStream, out *ImageStream, c *conversion.Cloner) error {
148 208
 	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
149 209
 		return err
... ...
@@ -432,6 +508,41 @@ func DeepCopy_v1_RepositoryImportStatus(in RepositoryImportStatus, out *Reposito
432 432
 	return nil
433 433
 }
434 434
 
435
+func DeepCopy_v1_SignatureCondition(in SignatureCondition, out *SignatureCondition, c *conversion.Cloner) error {
436
+	out.Type = in.Type
437
+	out.Status = in.Status
438
+	if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil {
439
+		return err
440
+	}
441
+	if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil {
442
+		return err
443
+	}
444
+	out.Reason = in.Reason
445
+	out.Message = in.Message
446
+	return nil
447
+}
448
+
449
+func DeepCopy_v1_SignatureGenericEntity(in SignatureGenericEntity, out *SignatureGenericEntity, c *conversion.Cloner) error {
450
+	out.Organization = in.Organization
451
+	out.CommonName = in.CommonName
452
+	return nil
453
+}
454
+
455
+func DeepCopy_v1_SignatureIssuer(in SignatureIssuer, out *SignatureIssuer, c *conversion.Cloner) error {
456
+	if err := DeepCopy_v1_SignatureGenericEntity(in.SignatureGenericEntity, &out.SignatureGenericEntity, c); err != nil {
457
+		return err
458
+	}
459
+	return nil
460
+}
461
+
462
+func DeepCopy_v1_SignatureSubject(in SignatureSubject, out *SignatureSubject, c *conversion.Cloner) error {
463
+	if err := DeepCopy_v1_SignatureGenericEntity(in.SignatureGenericEntity, &out.SignatureGenericEntity, c); err != nil {
464
+		return err
465
+	}
466
+	out.PublicKeyID = in.PublicKeyID
467
+	return nil
468
+}
469
+
435 470
 func DeepCopy_v1_TagEvent(in TagEvent, out *TagEvent, c *conversion.Cloner) error {
436 471
 	if err := unversioned.DeepCopy_unversioned_Time(in.Created, &out.Created, c); err != nil {
437 472
 		return err
... ...
@@ -26,6 +26,7 @@ var map_Image = map[string]string{
26 26
 	"dockerImageMetadataVersion": "DockerImageMetadataVersion conveys the version of the object, which if empty defaults to \"1.0\"",
27 27
 	"dockerImageManifest":        "DockerImageManifest is the raw JSON of the manifest",
28 28
 	"dockerImageLayers":          "DockerImageLayers represents the layers in the image. May not be set if the image does not define that data.",
29
+	"signatures":                 "Signatures holds all signatures of the image.",
29 30
 }
30 31
 
31 32
 func (Image) SwaggerDoc() map[string]string {
... ...
@@ -75,6 +76,22 @@ func (ImageList) SwaggerDoc() map[string]string {
75 75
 	return map_ImageList
76 76
 }
77 77
 
78
+var map_ImageSignature = map[string]string{
79
+	"":              "ImageSignature holds a signature of an image. It allows to verify image identity and possibly other claims as long as the signature is trusted. Based on this information it is possible to restrict runnable images to those matching cluster-wide policy. There are two mandatory fields provided by client: Type and Content. They should be parsed by clients doing image verification. The others are parsed from signature's content by the server. They serve just an informative purpose.",
80
+	"type":          "Required: Describes a type of stored blob.",
81
+	"content":       "Required: An opaque binary string which is an image's signature.",
82
+	"conditions":    "Conditions represent the latest available observations of a signature's current state.",
83
+	"imageIdentity": "A human readable string representing image's identity. It could be a product name and version, or an image pull spec (e.g. \"registry.access.redhat.com/rhel7/rhel:7.2\").",
84
+	"signedClaims":  "Contains claims from the signature.",
85
+	"created":       "If specified, it is the time of signature's creation.",
86
+	"issuedBy":      "If specified, it holds information about an issuer of signing certificate or key (a person or entity who signed the signing certificate or key).",
87
+	"issuedTo":      "If specified, it holds information about a subject of signing certificate or key (a person or entity who signed the image).",
88
+}
89
+
90
+func (ImageSignature) SwaggerDoc() map[string]string {
91
+	return map_ImageSignature
92
+}
93
+
78 94
 var map_ImageStream = map[string]string{
79 95
 	"":         "ImageStream stores a mapping of tags to images, metadata overrides that are applied when images are tagged in a stream, and an optional reference to a Docker image repository on a registry.",
80 96
 	"metadata": "Standard object's metadata.",
... ...
@@ -226,6 +243,47 @@ func (RepositoryImportStatus) SwaggerDoc() map[string]string {
226 226
 	return map_RepositoryImportStatus
227 227
 }
228 228
 
229
+var map_SignatureCondition = map[string]string{
230
+	"":                   "SignatureCondition describes an image signature condition of particular kind at particular probe time.",
231
+	"type":               "Type of job condition, Complete or Failed.",
232
+	"status":             "Status of the condition, one of True, False, Unknown.",
233
+	"lastProbeTime":      "Last time the condition was checked.",
234
+	"lastTransitionTime": "Last time the condition transit from one status to another.",
235
+	"reason":             "(brief) reason for the condition's last transition.",
236
+	"message":            "Human readable message indicating details about last transition.",
237
+}
238
+
239
+func (SignatureCondition) SwaggerDoc() map[string]string {
240
+	return map_SignatureCondition
241
+}
242
+
243
+var map_SignatureGenericEntity = map[string]string{
244
+	"":             "SignatureGenericEntity holds a generic information about a person or entity who is an issuer or a subject of signing certificate or key.",
245
+	"organization": "Organization name.",
246
+	"commonName":   "Common name (e.g. openshift-signing-service).",
247
+}
248
+
249
+func (SignatureGenericEntity) SwaggerDoc() map[string]string {
250
+	return map_SignatureGenericEntity
251
+}
252
+
253
+var map_SignatureIssuer = map[string]string{
254
+	"": "SignatureIssuer holds information about an issuer of signing certificate or key.",
255
+}
256
+
257
+func (SignatureIssuer) SwaggerDoc() map[string]string {
258
+	return map_SignatureIssuer
259
+}
260
+
261
+var map_SignatureSubject = map[string]string{
262
+	"":            "SignatureSubject holds information about a person or entity who created the signature.",
263
+	"publicKeyID": "If present, it is a human readable key id of public key belonging to the subject used to verify image signature. It should contain at least 64 lowest bits of public key's fingerprint (e.g. 0x685ebe62bf278440).",
264
+}
265
+
266
+func (SignatureSubject) SwaggerDoc() map[string]string {
267
+	return map_SignatureSubject
268
+}
269
+
229 270
 var map_TagEvent = map[string]string{
230 271
 	"":                     "TagEvent is used by ImageStreamStatus to keep a historical record of images associated with a tag.",
231 272
 	"created":              "Created holds the time the TagEvent was created",
... ...
@@ -32,6 +32,8 @@ type Image struct {
32 32
 	DockerImageManifest string `json:"dockerImageManifest,omitempty"`
33 33
 	// DockerImageLayers represents the layers in the image. May not be set if the image does not define that data.
34 34
 	DockerImageLayers []ImageLayer `json:"dockerImageLayers"`
35
+	// Signatures holds all signatures of the image.
36
+	Signatures []ImageSignature `json:"signatures,omitempty"`
35 37
 }
36 38
 
37 39
 // ImageLayer represents a single layer of the image. Some images may have multiple layers. Some may have none.
... ...
@@ -42,6 +44,80 @@ type ImageLayer struct {
42 42
 	Size int64 `json:"size"`
43 43
 }
44 44
 
45
+// ImageSignature holds a signature of an image. It allows to verify image identity and possibly other claims
46
+// as long as the signature is trusted. Based on this information it is possible to restrict runnable images
47
+// to those matching cluster-wide policy.
48
+// There are two mandatory fields provided by client: Type and Content. They should be parsed by clients doing
49
+// image verification. The others are parsed from signature's content by the server. They serve just an
50
+// informative purpose.
51
+type ImageSignature struct {
52
+	// Required: Describes a type of stored blob.
53
+	Type string `json:"type"`
54
+	// Required: An opaque binary string which is an image's signature.
55
+	Content []byte `json:"content"`
56
+	// Conditions represent the latest available observations of a signature's current state.
57
+	Conditions []SignatureCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
58
+
59
+	// Following metadata fields will be set by server if the signature content is successfully parsed and
60
+	// the information available.
61
+
62
+	// A human readable string representing image's identity. It could be a product name and version, or an
63
+	// image pull spec (e.g. "registry.access.redhat.com/rhel7/rhel:7.2").
64
+	ImageIdentity string `json:"imageIdentity,omitempty"`
65
+	// Contains claims from the signature.
66
+	SignedClaims map[string]string `json:"signedClaims,omitempty"`
67
+	// If specified, it is the time of signature's creation.
68
+	Created *unversioned.Time `json:"created,omitempty"`
69
+	// If specified, it holds information about an issuer of signing certificate or key (a person or entity
70
+	// who signed the signing certificate or key).
71
+	IssuedBy *SignatureIssuer `json:"issuedBy,omitempty"`
72
+	// If specified, it holds information about a subject of signing certificate or key (a person or entity
73
+	// who signed the image).
74
+	IssuedTo *SignatureSubject `json:"issuedTo,omitempty"`
75
+}
76
+
77
+/// SignatureConditionType is a type of image signature condition.
78
+type SignatureConditionType string
79
+
80
+// SignatureCondition describes an image signature condition of particular kind at particular probe time.
81
+type SignatureCondition struct {
82
+	// Type of job condition, Complete or Failed.
83
+	Type SignatureConditionType `json:"type"`
84
+	// Status of the condition, one of True, False, Unknown.
85
+	Status kapi.ConditionStatus `json:"status"`
86
+	// Last time the condition was checked.
87
+	LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"`
88
+	// Last time the condition transit from one status to another.
89
+	LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"`
90
+	// (brief) reason for the condition's last transition.
91
+	Reason string `json:"reason,omitempty"`
92
+	// Human readable message indicating details about last transition.
93
+	Message string `json:"message,omitempty"`
94
+}
95
+
96
+// SignatureGenericEntity holds a generic information about a person or entity who is an issuer or a subject
97
+// of signing certificate or key.
98
+type SignatureGenericEntity struct {
99
+	// Organization name.
100
+	Organization string `json:"organization,omitempty"`
101
+	// Common name (e.g. openshift-signing-service).
102
+	CommonName string `json:"commonName,omitempty"`
103
+}
104
+
105
+// SignatureIssuer holds information about an issuer of signing certificate or key.
106
+type SignatureIssuer struct {
107
+	SignatureGenericEntity `json:",inline"`
108
+}
109
+
110
+// SignatureSubject holds information about a person or entity who created the signature.
111
+type SignatureSubject struct {
112
+	SignatureGenericEntity `json:",inline"`
113
+	// If present, it is a human readable key id of public key belonging to the subject used to verify image
114
+	// signature. It should contain at least 64 lowest bits of public key's fingerprint (e.g.
115
+	// 0x685ebe62bf278440).
116
+	PublicKeyID string `json:"publicKeyID"`
117
+}
118
+
45 119
 // ImageStreamList is a list of ImageStream objects.
46 120
 type ImageStreamList struct {
47 121
 	unversioned.TypeMeta `json:",inline"`
... ...
@@ -29,6 +29,8 @@ type Image struct {
29 29
 	DockerImageManifest string `json:"dockerImageManifest,omitempty"`
30 30
 	// DockerImageLayers represents the layers in the image. May not be set if the image does not define that data.
31 31
 	DockerImageLayers []ImageLayer `json:"dockerImageLayers"`
32
+	// Signatures holds all signatures of the image.
33
+	Signatures []ImageSignature
32 34
 }
33 35
 
34 36
 // ImageLayer represents a single layer of the image. Some images may have multiple layers. Some may have none.
... ...
@@ -39,6 +41,80 @@ type ImageLayer struct {
39 39
 	Size int64 `json:"size"`
40 40
 }
41 41
 
42
+// ImageSignature holds a signature of an image. It allows to verify image identity and possibly other claims
43
+// as long as the signature is trusted. Based on this information it is possible to restrict runnable images
44
+// to those matching cluster-wide policy.
45
+// There are two mandatory fields provided by client: Type and Content. They should be parsed by clients doing
46
+// image verification. The others are parsed from signature's content by the server. They serve just an
47
+// informative purpose.
48
+type ImageSignature struct {
49
+	// Required: Describes a type of stored blob.
50
+	Type string `json:"type"`
51
+	// Required: An opaque binary string which is an image's signature.
52
+	Content []byte `json:"content"`
53
+	// Conditions represent the latest available observations of a signature's current state.
54
+	Conditions []SignatureCondition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
55
+
56
+	// Following metadata fields will be set by server if the signature content is successfully parsed and
57
+	// the information available.
58
+
59
+	// A human readable string representing image's identity. It could be a product name and version, or an
60
+	// image pull spec (e.g. "registry.access.redhat.com/rhel7/rhel:7.2").
61
+	ImageIdentity string `json:"imageIdentity,omitempty"`
62
+	// Contains claims from the signature.
63
+	SignedClaims map[string]string `json:"signedClaims,omitempty"`
64
+	// If specified, it is the time of signature's creation.
65
+	Created *unversioned.Time `json:"created,omitempty"`
66
+	// If specified, it holds information about an issuer of signing certificate or key (a person or entity
67
+	// who signed the signing certificate or key).
68
+	IssuedBy *SignatureIssuer `json:"issuedBy,omitempty"`
69
+	// If specified, it holds information about a subject of signing certificate or key (a person or entity
70
+	// who signed the image).
71
+	IssuedTo *SignatureSubject `json:"issuedTo,omitempty"`
72
+}
73
+
74
+/// SignatureConditionType is a type of image signature condition.
75
+type SignatureConditionType string
76
+
77
+// SignatureCondition describes an image signature condition of particular kind at particular probe time.
78
+type SignatureCondition struct {
79
+	// Type of job condition, Complete or Failed.
80
+	Type SignatureConditionType `json:"type"`
81
+	// Status of the condition, one of True, False, Unknown.
82
+	Status kapi.ConditionStatus `json:"status"`
83
+	// Last time the condition was checked.
84
+	LastProbeTime unversioned.Time `json:"lastProbeTime,omitempty"`
85
+	// Last time the condition transit from one status to another.
86
+	LastTransitionTime unversioned.Time `json:"lastTransitionTime,omitempty"`
87
+	// (brief) reason for the condition's last transition.
88
+	Reason string `json:"reason,omitempty"`
89
+	// Human readable message indicating details about last transition.
90
+	Message string `json:"message,omitempty"`
91
+}
92
+
93
+// SignatureGenericEntity holds a generic information about a person or entity who is an issuer or a subject
94
+// of signing certificate or key.
95
+type SignatureGenericEntity struct {
96
+	// Organization name.
97
+	Organization string `json:"organization,omitempty"`
98
+	// Common name (e.g. openshift-signing-service).
99
+	CommonName string `json:"commonName,omitempty"`
100
+}
101
+
102
+// SignatureIssuer holds information about an issuer of signing certificate or key.
103
+type SignatureIssuer struct {
104
+	SignatureGenericEntity `json:",inline"`
105
+}
106
+
107
+// SignatureSubject holds information about a person or entity who created the signature.
108
+type SignatureSubject struct {
109
+	SignatureGenericEntity `json:",inline"`
110
+	// If present, it is a human readable key id of public key belonging to the subject used to verify image
111
+	// signature. It should contain at least 64 lowest bits of public key's fingerprint (e.g.
112
+	// 0x685ebe62bf278440).
113
+	PublicKeyID string `json:"publicKeyID"`
114
+}
115
+
42 116
 // ImageStreamList is a list of ImageStream objects.
43 117
 type ImageStreamList struct {
44 118
 	unversioned.TypeMeta `json:",inline"`
... ...
@@ -59,9 +59,59 @@ func validateImage(image *api.Image, fldPath *field.Path) field.ErrorList {
59 59
 		}
60 60
 	}
61 61
 
62
+	for i, sig := range image.Signatures {
63
+		result = append(result, validateImageSignature(&sig, fldPath.Child("signatures").Index(i))...)
64
+	}
65
+
62 66
 	return result
63 67
 }
64 68
 
69
+func validateImageSignature(signature *api.ImageSignature, fldPath *field.Path) field.ErrorList {
70
+	allErrs := field.ErrorList{}
71
+
72
+	if len(signature.Type) == 0 {
73
+		allErrs = append(allErrs, field.Required(fldPath.Child("type"), ""))
74
+	}
75
+	if len(signature.Content) == 0 {
76
+		allErrs = append(allErrs, field.Required(fldPath.Child("content"), ""))
77
+	}
78
+
79
+	var trustedCondition, forImageCondition *api.SignatureCondition
80
+	for i := range signature.Conditions {
81
+		cond := &signature.Conditions[i]
82
+		if cond.Type == api.SignatureTrusted && (trustedCondition == nil || !cond.LastProbeTime.Before(trustedCondition.LastProbeTime)) {
83
+			trustedCondition = cond
84
+		} else if cond.Type == api.SignatureForImage && forImageCondition == nil || !cond.LastProbeTime.Before(forImageCondition.LastProbeTime) {
85
+			forImageCondition = cond
86
+		}
87
+	}
88
+
89
+	if trustedCondition != nil && forImageCondition == nil {
90
+		msg := fmt.Sprintf("missing %q condition type", api.SignatureForImage)
91
+		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), signature.Conditions, msg))
92
+	} else if forImageCondition != nil && trustedCondition == nil {
93
+		msg := fmt.Sprintf("missing %q condition type", api.SignatureTrusted)
94
+		allErrs = append(allErrs, field.Invalid(fldPath.Child("conditions"), signature.Conditions, msg))
95
+	}
96
+
97
+	if trustedCondition == nil || trustedCondition.Status == kapi.ConditionUnknown {
98
+		if len(signature.ImageIdentity) != 0 {
99
+			allErrs = append(allErrs, field.Invalid(fldPath.Child("imageIdentity"), signature.ImageIdentity, "must be unset for unknown signature state"))
100
+		}
101
+		if len(signature.SignedClaims) != 0 {
102
+			allErrs = append(allErrs, field.Invalid(fldPath.Child("signedClaims"), signature.SignedClaims, "must be unset for unknown signature state"))
103
+		}
104
+		if signature.IssuedBy != nil {
105
+			allErrs = append(allErrs, field.Invalid(fldPath.Child("issuedBy"), signature.IssuedBy, "must be unset for unknown signature state"))
106
+		}
107
+		if signature.IssuedTo != nil {
108
+			allErrs = append(allErrs, field.Invalid(fldPath.Child("issuedTo"), signature.IssuedTo, "must be unset for unknown signature state"))
109
+		}
110
+	}
111
+
112
+	return allErrs
113
+}
114
+
65 115
 func ValidateImageUpdate(newImage, oldImage *api.Image) field.ErrorList {
66 116
 	result := validation.ValidateObjectMetaUpdate(&newImage.ObjectMeta, &oldImage.ObjectMeta, field.NewPath("metadata"))
67 117
 	result = append(result, ValidateImage(newImage)...)
... ...
@@ -1,14 +1,16 @@
1 1
 package validation
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"reflect"
5 6
 	"strings"
6 7
 	"testing"
7 8
 
8
-	"github.com/openshift/origin/pkg/image/api"
9 9
 	kapi "k8s.io/kubernetes/pkg/api"
10 10
 	"k8s.io/kubernetes/pkg/util/diff"
11 11
 	"k8s.io/kubernetes/pkg/util/validation/field"
12
+
13
+	"github.com/openshift/origin/pkg/image/api"
12 14
 )
13 15
 
14 16
 func TestValidateImageOK(t *testing.T) {
... ...
@@ -68,6 +70,149 @@ func TestValidateImageMissingFields(t *testing.T) {
68 68
 	}
69 69
 }
70 70
 
71
+func TestValidateImageSignature(t *testing.T) {
72
+	for _, tc := range []struct {
73
+		name      string
74
+		signature api.ImageSignature
75
+		expected  field.ErrorList
76
+	}{
77
+		{
78
+			name: "valid",
79
+			signature: api.ImageSignature{
80
+				Type:    "valid",
81
+				Content: []byte("blob"),
82
+			},
83
+			expected: field.ErrorList{},
84
+		},
85
+
86
+		{
87
+			name: "valid trusted",
88
+			signature: api.ImageSignature{
89
+				Type:    "valid",
90
+				Content: []byte("blob"),
91
+				Conditions: []api.SignatureCondition{
92
+					{
93
+						Type:   api.SignatureTrusted,
94
+						Status: kapi.ConditionTrue,
95
+					},
96
+					{
97
+						Type:   api.SignatureForImage,
98
+						Status: kapi.ConditionTrue,
99
+					},
100
+				},
101
+				ImageIdentity: "registry.company.ltd/app/core:v1.2",
102
+			},
103
+			expected: field.ErrorList{},
104
+		},
105
+
106
+		{
107
+			name: "valid untrusted",
108
+			signature: api.ImageSignature{
109
+				Type:    "valid",
110
+				Content: []byte("blob"),
111
+				Conditions: []api.SignatureCondition{
112
+					{
113
+						Type:   api.SignatureTrusted,
114
+						Status: kapi.ConditionTrue,
115
+					},
116
+					{
117
+						Type:   api.SignatureForImage,
118
+						Status: kapi.ConditionFalse,
119
+					},
120
+					// compare the latest condition
121
+					{
122
+						Type:   api.SignatureTrusted,
123
+						Status: kapi.ConditionFalse,
124
+					},
125
+				},
126
+				ImageIdentity: "registry.company.ltd/app/core:v1.2",
127
+			},
128
+			expected: field.ErrorList{},
129
+		},
130
+
131
+		{
132
+			name: "missing type",
133
+			signature: api.ImageSignature{
134
+				Content: []byte("blob"),
135
+			},
136
+			expected: field.ErrorList{
137
+				field.Required(field.NewPath("type"), ""),
138
+			},
139
+		},
140
+
141
+		{
142
+			name: "missing content",
143
+			signature: api.ImageSignature{
144
+				Type: "invalid",
145
+			},
146
+			expected: field.ErrorList{
147
+				field.Required(field.NewPath("content"), ""),
148
+			},
149
+		},
150
+
151
+		{
152
+			name: "missing ForImage condition",
153
+			signature: api.ImageSignature{
154
+				Type:    "invalid",
155
+				Content: []byte("blob"),
156
+				Conditions: []api.SignatureCondition{
157
+					{
158
+						Type:   api.SignatureTrusted,
159
+						Status: kapi.ConditionTrue,
160
+					},
161
+				},
162
+				ImageIdentity: "registry.company.ltd/app/core:v1.2",
163
+			},
164
+			expected: field.ErrorList{field.Invalid(field.NewPath("conditions"),
165
+				[]api.SignatureCondition{
166
+					{
167
+						Type:   api.SignatureTrusted,
168
+						Status: kapi.ConditionTrue,
169
+					},
170
+				},
171
+				fmt.Sprintf("missing %q condition type", api.SignatureForImage))},
172
+		},
173
+
174
+		{
175
+			name: "filled metadata for unknown signature state",
176
+			signature: api.ImageSignature{
177
+				Type:    "invalid",
178
+				Content: []byte("blob"),
179
+				Conditions: []api.SignatureCondition{
180
+					{
181
+						Type:   api.SignatureTrusted,
182
+						Status: kapi.ConditionUnknown,
183
+					},
184
+					{
185
+						Type:   api.SignatureForImage,
186
+						Status: kapi.ConditionUnknown,
187
+					},
188
+				},
189
+				ImageIdentity: "registry.company.ltd/app/core:v1.2",
190
+				SignedClaims:  map[string]string{"claim": "value"},
191
+				IssuedBy: &api.SignatureIssuer{
192
+					SignatureGenericEntity: api.SignatureGenericEntity{Organization: "org"},
193
+				},
194
+				IssuedTo: &api.SignatureSubject{PublicKeyID: "id"},
195
+			},
196
+			expected: field.ErrorList{
197
+				field.Invalid(field.NewPath("imageIdentity"), "registry.company.ltd/app/core:v1.2", "must be unset for unknown signature state"),
198
+				field.Invalid(field.NewPath("signedClaims"), map[string]string{"claim": "value"}, "must be unset for unknown signature state"),
199
+				field.Invalid(field.NewPath("issuedBy"), &api.SignatureIssuer{
200
+					SignatureGenericEntity: api.SignatureGenericEntity{Organization: "org"},
201
+				}, "must be unset for unknown signature state"),
202
+				field.Invalid(field.NewPath("issuedTo"), &api.SignatureSubject{PublicKeyID: "id"}, "must be unset for unknown signature state"),
203
+			},
204
+		},
205
+	} {
206
+		errs := validateImageSignature(&tc.signature, nil)
207
+		if e, a := tc.expected, errs; !reflect.DeepEqual(a, e) {
208
+			t.Errorf("[%s] unexpected errors: %s", tc.name, diff.ObjectDiff(e, a))
209
+		}
210
+	}
211
+
212
+}
213
+
71 214
 func TestValidateImageStreamMappingNotOK(t *testing.T) {
72 215
 	errorCases := map[string]struct {
73 216
 		I api.ImageStreamMapping
... ...
@@ -32,12 +32,14 @@ func (imageStrategy) NamespaceScoped() bool {
32 32
 
33 33
 // PrepareForCreate clears fields that are not allowed to be set by end users on creation.
34 34
 // It extracts the latest information from the manifest (if available) and sets that onto the object.
35
-func (imageStrategy) PrepareForCreate(obj runtime.Object) {
35
+func (s imageStrategy) PrepareForCreate(obj runtime.Object) {
36 36
 	newImage := obj.(*api.Image)
37 37
 	// ignore errors, change in place
38 38
 	if err := api.ImageWithMetadata(newImage); err != nil {
39 39
 		utilruntime.HandleError(fmt.Errorf("Unable to update image metadata for %q: %v", newImage.Name, err))
40 40
 	}
41
+
42
+	s.clearSignatureDetails(newImage)
41 43
 }
42 44
 
43 45
 // Validate validates a new image.
... ...
@@ -72,6 +74,7 @@ func (imageStrategy) PrepareForUpdate(obj, old runtime.Object) {
72 72
 	newImage.DockerImageMetadata = oldImage.DockerImageMetadata
73 73
 	newImage.DockerImageMetadataVersion = oldImage.DockerImageMetadataVersion
74 74
 	newImage.DockerImageLayers = oldImage.DockerImageLayers
75
+	newImage.Signatures = oldImage.Signatures
75 76
 
76 77
 	// allow an image update that results in the manifest matching the digest (the name)
77 78
 	newManifest := newImage.DockerImageManifest
... ...
@@ -95,6 +98,20 @@ func (imageStrategy) ValidateUpdate(ctx kapi.Context, obj, old runtime.Object) f
95 95
 	return validation.ValidateImageUpdate(old.(*api.Image), obj.(*api.Image))
96 96
 }
97 97
 
98
+// clearSignatureDetails removes signature details from all the signatures of given image. It also clear all
99
+// the validation data. These data will be set by the server once the signature parsing support is added.
100
+func (imageStrategy) clearSignatureDetails(image *api.Image) {
101
+	for i := range image.Signatures {
102
+		signature := &image.Signatures[i]
103
+		signature.Conditions = nil
104
+		signature.ImageIdentity = ""
105
+		signature.SignedClaims = nil
106
+		signature.Created = nil
107
+		signature.IssuedBy = nil
108
+		signature.IssuedTo = nil
109
+	}
110
+}
111
+
98 112
 // MatchImage returns a generic matcher for a given label and field selector.
99 113
 func MatchImage(label labels.Selector, field fields.Selector) generic.Matcher {
100 114
 	return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {