Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions api/swagger-spec/oapi-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -21059,6 +21059,13 @@
"$ref": "v1.ImageLayer"
},
"description": "DockerImageLayers represents the layers in the image. May not be set if the image does not define that data."
},
"signatures": {
"type": "array",
"items": {
"$ref": "v1.ImageSignature"
},
"description": "Signatures holds all signatures of the image."
}
}
},
Expand All @@ -21081,6 +21088,123 @@
}
}
},
"v1.ImageSignature": {
"id": "v1.ImageSignature",
"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.",
"required": [
"type",
"content"
],
"properties": {
"type": {
"type": "string",
"description": "Required: Describes a type of stored blob."
},
"content": {
"type": "array",
"items": {
"$ref": "integer"
},
"description": "Required: An opaque binary string which is an image's signature."
},
"conditions": {
"type": "array",
"items": {
"$ref": "v1.SignatureCondition"
},
"description": "Conditions represent the latest available observations of a signature's current state."
},
"imageIdentity": {
"type": "string",
"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\")."
},
"signedClaims": {
"type": "any",
"description": "Contains claims from the signature."
},
"created": {
"type": "string",
"description": "If specified, it is the time of signature's creation."
},
"issuedBy": {
"$ref": "v1.SignatureIssuer",
"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)."
},
"issuedTo": {
"$ref": "v1.SignatureSubject",
"description": "If specified, it holds information about a subject of signing certificate or key (a person or entity who signed the image)."
}
}
},
"v1.SignatureCondition": {
"id": "v1.SignatureCondition",
"description": "SignatureCondition describes an image signature condition of particular kind at particular probe time.",
"required": [
"type",
"status"
],
"properties": {
"type": {
"type": "string",
"description": "Type of job condition, Complete or Failed."
},
"status": {
"type": "string",
"description": "Status of the condition, one of True, False, Unknown."
},
"lastProbeTime": {
"type": "string",
"description": "Last time the condition was checked."
},
"lastTransitionTime": {
"type": "string",
"description": "Last time the condition transit from one status to another."
},
"reason": {
"type": "string",
"description": "(brief) reason for the condition's last transition."
},
"message": {
"type": "string",
"description": "Human readable message indicating details about last transition."
}
}
},
"v1.SignatureIssuer": {
"id": "v1.SignatureIssuer",
"description": "SignatureIssuer holds information about an issuer of signing certificate or key.",
"properties": {
"organization": {
"type": "string",
"description": "Organization name."
},
"commonName": {
"type": "string",
"description": "Common name (e.g. openshift-signing-service)."
}
}
},
"v1.SignatureSubject": {
"id": "v1.SignatureSubject",
"description": "SignatureSubject holds information about a person or entity who created the signature.",
"required": [
"publicKeyID"
],
"properties": {
"organization": {
"type": "string",
"description": "Organization name."
},
"commonName": {
"type": "string",
"description": "Common name (e.g. openshift-signing-service)."
},
"publicKeyID": {
"type": "string",
"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)."
}
}
},
"v1.ImageStreamImage": {
"id": "v1.ImageStreamImage",
"description": "ImageStreamImage represents an Image that is retrieved by image name from an ImageStream.",
Expand Down
111 changes: 111 additions & 0 deletions pkg/image/api/deep_copy_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func init() {
DeepCopy_api_ImageImportStatus,
DeepCopy_api_ImageLayer,
DeepCopy_api_ImageList,
DeepCopy_api_ImageSignature,
DeepCopy_api_ImageStream,
DeepCopy_api_ImageStreamImage,
DeepCopy_api_ImageStreamImport,
Expand All @@ -39,6 +40,10 @@ func init() {
DeepCopy_api_ImageStreamTagList,
DeepCopy_api_RepositoryImportSpec,
DeepCopy_api_RepositoryImportStatus,
DeepCopy_api_SignatureCondition,
DeepCopy_api_SignatureGenericEntity,
DeepCopy_api_SignatureIssuer,
DeepCopy_api_SignatureSubject,
DeepCopy_api_TagEvent,
DeepCopy_api_TagEventCondition,
DeepCopy_api_TagEventList,
Expand Down Expand Up @@ -312,6 +317,17 @@ func DeepCopy_api_Image(in Image, out *Image, c *conversion.Cloner) error {
} else {
out.DockerImageLayers = nil
}
if in.Signatures != nil {
in, out := in.Signatures, &out.Signatures
*out = make([]ImageSignature, len(in))
for i := range in {
if err := DeepCopy_api_ImageSignature(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Signatures = nil
}
return nil
}

Expand Down Expand Up @@ -379,6 +395,66 @@ func DeepCopy_api_ImageList(in ImageList, out *ImageList, c *conversion.Cloner)
return nil
}

func DeepCopy_api_ImageSignature(in ImageSignature, out *ImageSignature, c *conversion.Cloner) error {
out.Type = in.Type
if in.Content != nil {
in, out := in.Content, &out.Content
*out = make([]byte, len(in))
copy(*out, in)
} else {
out.Content = nil
}
if in.Conditions != nil {
in, out := in.Conditions, &out.Conditions
*out = make([]SignatureCondition, len(in))
for i := range in {
if err := DeepCopy_api_SignatureCondition(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Conditions = nil
}
out.ImageIdentity = in.ImageIdentity
if in.SignedClaims != nil {
in, out := in.SignedClaims, &out.SignedClaims
*out = make(map[string]string)
for key, val := range in {
(*out)[key] = val
}
} else {
out.SignedClaims = nil
}
if in.Created != nil {
in, out := in.Created, &out.Created
*out = new(unversioned.Time)
if err := unversioned.DeepCopy_unversioned_Time(*in, *out, c); err != nil {
return err
}
} else {
out.Created = nil
}
if in.IssuedBy != nil {
in, out := in.IssuedBy, &out.IssuedBy
*out = new(SignatureIssuer)
if err := DeepCopy_api_SignatureIssuer(*in, *out, c); err != nil {
return err
}
} else {
out.IssuedBy = nil
}
if in.IssuedTo != nil {
in, out := in.IssuedTo, &out.IssuedTo
*out = new(SignatureSubject)
if err := DeepCopy_api_SignatureSubject(*in, *out, c); err != nil {
return err
}
} else {
out.IssuedTo = nil
}
return nil
}

func DeepCopy_api_ImageStream(in ImageStream, out *ImageStream, c *conversion.Cloner) error {
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
Expand Down Expand Up @@ -645,6 +721,41 @@ func DeepCopy_api_RepositoryImportStatus(in RepositoryImportStatus, out *Reposit
return nil
}

func DeepCopy_api_SignatureCondition(in SignatureCondition, out *SignatureCondition, c *conversion.Cloner) error {
out.Type = in.Type
out.Status = in.Status
if err := unversioned.DeepCopy_unversioned_Time(in.LastProbeTime, &out.LastProbeTime, c); err != nil {
return err
}
if err := unversioned.DeepCopy_unversioned_Time(in.LastTransitionTime, &out.LastTransitionTime, c); err != nil {
return err
}
out.Reason = in.Reason
out.Message = in.Message
return nil
}

func DeepCopy_api_SignatureGenericEntity(in SignatureGenericEntity, out *SignatureGenericEntity, c *conversion.Cloner) error {
out.Organization = in.Organization
out.CommonName = in.CommonName
return nil
}

func DeepCopy_api_SignatureIssuer(in SignatureIssuer, out *SignatureIssuer, c *conversion.Cloner) error {
if err := DeepCopy_api_SignatureGenericEntity(in.SignatureGenericEntity, &out.SignatureGenericEntity, c); err != nil {
return err
}
return nil
}

func DeepCopy_api_SignatureSubject(in SignatureSubject, out *SignatureSubject, c *conversion.Cloner) error {
if err := DeepCopy_api_SignatureGenericEntity(in.SignatureGenericEntity, &out.SignatureGenericEntity, c); err != nil {
return err
}
out.PublicKeyID = in.PublicKeyID
return nil
}

func DeepCopy_api_TagEvent(in TagEvent, out *TagEvent, c *conversion.Cloner) error {
if err := unversioned.DeepCopy_unversioned_Time(in.Created, &out.Created, c); err != nil {
return err
Expand Down
95 changes: 95 additions & 0 deletions pkg/image/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ type Image struct {
DockerImageManifest string
// DockerImageLayers represents the layers in the image. May not be set if the image does not define that data.
DockerImageLayers []ImageLayer
// Signatures holds all signatures of the image.
Signatures []ImageSignature
}

// ImageLayer represents a single layer of the image. Some images may have multiple layers. Some may have none.
Expand All @@ -76,6 +78,99 @@ type ImageLayer struct {
Size int64
}

const (
// The supported type of image signature.
ImageSignatureTypeAtomicImageV1 string = "AtomicImageV1"
)

// 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.
type ImageSignature struct {
Copy link
Contributor

@deads2k deads2k May 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should usually be able to get a SignerSubject and CertificateSubject right? That would tell me which signing key was used and the CertificateSubject would let me know who confirmed the cert.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, GPG signatures do not carry full public keys, only key IDs, so we don’t automatically know the “certificate subject” (text describing the public key).

Of course if the public key is trusted per policy, the policy will have a copy of the key. Or we could ask a key server, and perhaps get a copy.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course if the public key is trusted per policy, the policy will have a copy of the key. Or we could ask a key server, and perhaps get a copy.

I think the field is useful enough to have and if the server has sufficient information to complete it, it should do so. Knowing it was signed by openshift-builder or mycompany-operations is valuable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the field is useful enough to have and if the server has sufficient information to complete it, it should do so. Knowing it was signed by openshift-builder or mycompany-operations is valuable.

Maybe a full explosion of "Issued By" and "Issued To"

// Required: Describes a type of stored blob.
Type string
// Required: An opaque binary string which is an image's signature.
Content []byte
// Conditions represent the latest available observations of a signature's current state.
Conditions []SignatureCondition

// Following metadata fields will be set by server if the signature content is successfully parsed and
// the information available.

// 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").
ImageIdentity string
// Contains claims from the signature.
SignedClaims map[string]string
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will hold arbitrary new claims added in the future that are interesting but not standard enough to become first-class fields. All the claims, once stored, will need to be kept in this mapping as well - even if they later become first-class fields.

// If specified, it is the time of signature's creation.
Created *unversioned.Time
// If specified, it holds information about an issuer of signing certificate or key (a person or entity
// who signed the signing certificate or key).
IssuedBy *SignatureIssuer
// If specified, it holds information about a subject of signing certificate or key (a person or entity
// who signed the image).
IssuedTo *SignatureSubject
}

// These are valid conditions of an image signature.
const (
// SignatureTrusted means the signing key or certificate was valid and the signature matched the image at
// the probe time.
SignatureTrusted = "Trusted"
// SignatureForImage means the signature matches image object containing it.
SignatureForImage = "ForImage"
// SignatureExpired means the signature or its signing key or certificate had been expired at the probe
// time.
SignatureExpired = "Expired"
// SignatureRevoked means the signature or its signing key or certificate has been revoked.
SignatureRevoked = "Revoked"
)

/// SignatureConditionType is a type of image signature condition.
type SignatureConditionType string

// SignatureCondition describes an image signature condition of particular kind at particular probe time.
type SignatureCondition struct {
// Type of job condition, Complete or Failed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cp error

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cp error

Thanks! Addressed in #9181

Type SignatureConditionType
// Status of the condition, one of True, False, Unknown.
Status kapi.ConditionStatus
// Last time the condition was checked.
LastProbeTime unversioned.Time
// Last time the condition transit from one status to another.
LastTransitionTime unversioned.Time
// (brief) reason for the condition's last transition.
Reason string
// Human readable message indicating details about last transition.
Message string
}

// SignatureGenericEntity holds a generic information about a person or entity who is an issuer or a subject
// of signing certificate or key.
type SignatureGenericEntity struct {
// Organization name.
Organization string
// Common name (e.g. openshift-signing-service).
CommonName string
}

// SignatureIssuer holds information about an issuer of signing certificate or key.
type SignatureIssuer struct {
SignatureGenericEntity
}

// SignatureSubject holds information about a person or entity who created the signature.
type SignatureSubject struct {
SignatureGenericEntity
// 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).
PublicKeyID string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this would be ID vs Fingerprint? Is ID in common use to describe this field?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just trying to establish what someone familiar with signatures would expect in the usage here - fingerprint vs id.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key ID is a shortened key fingerprint. Usually its lowest 32 or 64 bits. My reasoning for including ID instead of fingerprint is that this field is meant to be for diplaying. Not for verification. Clients that want to verify the signature should deal with Content field. What you usually see while working with gpg keys is just key ids because they are relatively short.

$ gpg2 --list-keys
/home/miminar/.gnupg/pubring.gpg
--------------------------------
pub   rsa2048/71445503 2014-01-13 [SC] [expired: 2016-01-13]
uid         [ expired] Michal Minar <[email protected]>
...
$ gpg --fingerprint 0x71445503
pub   rsa2048/71445503 2014-01-13 [SC] [expired: 2016-01-13]
      Key fingerprint = BF4A E972 503B 49BC A6D2  6836 F7B3 5329 7144 5503
uid         [ expired] Michal Minar <[email protected]>

The full fingerprint may be very long depending on algorithm used. It this were PublicKeyFingerprint, I'd rather store it in binary format []byte and let the rendering client choose whatever formating it prefers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that's the clarity I wanted.

}

// ImageStreamList is a list of ImageStream objects.
type ImageStreamList struct {
unversioned.TypeMeta
Expand Down
Loading