Skip to content

Commit

Permalink
Passing --to-signature to release new creates the signature file
Browse files Browse the repository at this point in the history
The signature file can be signed by an atomic container image signer
as an OpenPGP message in order to verify update payloads.
  • Loading branch information
smarterclayton committed Apr 25, 2019
1 parent c0c0569 commit 2522ba4
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 0 deletions.
2 changes: 2 additions & 0 deletions contrib/completions/bash/oc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions contrib/completions/zsh/oc

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions pkg/oc/cli/admin/release/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/pkg/version"
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/util/templates"

Expand Down Expand Up @@ -145,6 +146,7 @@ func NewRelease(f kcmdutil.Factory, parentName string, streams genericclioptions
flags.StringVar(&o.ToImage, "to-image", o.ToImage, "The location to upload the release image to.")
flags.StringVar(&o.ToImageBase, "to-image-base", o.ToImageBase, "If specified, the image to add the release layer on top of.")
flags.StringVar(&o.ToImageBaseTag, "to-image-base-tag", o.ToImageBaseTag, "If specified, the image tag in the input to add the release layer on top of. Defaults to cluster-version-operator.")
flags.StringVar(&o.ToSignature, "to-signature", o.ToSignature, "If specified, output a message that can be signed that describes this release. Requires --to-image.")

// misc
flags.StringVarP(&o.Output, "output", "o", o.Output, "Output the mapping definition in this format.")
Expand Down Expand Up @@ -189,6 +191,7 @@ type NewOptions struct {
ToImage string
ToImageBase string
ToImageBaseTag string
ToSignature string

Mirror string

Expand Down Expand Up @@ -262,6 +265,9 @@ func (o *NewOptions) Validate() error {
return fmt.Errorf("must specify image mappings when no other source is defined")
}
}
if len(o.ToSignature) > 0 && len(o.ToImage) == 0 {
return fmt.Errorf("--to-signature requires --to-image")
}
if len(o.Mirror) > 0 && o.ReferenceMode != "" && o.ReferenceMode != "public" {
return fmt.Errorf("--reference-mode must be public or empty when using --mirror")
}
Expand Down Expand Up @@ -1160,6 +1166,22 @@ func (o *NewOptions) write(r io.Reader, is *imageapi.ImageStream, now time.Time)
fmt.Fprintf(o.ErrOut, "warning: %v\n", err)
}

// TODO: support a dry run that doesn't push the image, but requires append to have a dry-run mode
toRefWithDigest := toRef
toRefWithDigest.Tag = ""
toRefWithDigest.ID = options.ToDigest.String()
msg, err := createReleaseSignatureMessage(fmt.Sprintf("oc-adm-release-new/%s", version.Get().GitCommit), now, options.ToDigest.String(), toRefWithDigest.Exact())
if err != nil {
return err
}
if len(o.ToSignature) > 0 {
if err := ioutil.WriteFile(o.ToSignature, msg, 0644); err != nil {
return fmt.Errorf("unable to write signature file: %v", err)
}
} else {
klog.V(2).Infof("Signature for output:\n%s", string(msg))
}

default:
fmt.Fprintf(o.ErrOut, "info: Extracting operator contents to disk without building a release artifact\n")
if _, err := io.Copy(ioutil.Discard, r); err != nil {
Expand Down
72 changes: 72 additions & 0 deletions pkg/oc/cli/admin/release/signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package release

import (
"encoding/json"
"fmt"
"time"
)

// createReleaseSignatureMessage creates the core message to sign the release payload.
func createReleaseSignatureMessage(signer string, now time.Time, releaseDigest, pullSpec string) ([]byte, error) {
if len(signer) == 0 || now.IsZero() || len(releaseDigest) == 0 || len(pullSpec) == 0 {
return nil, fmt.Errorf("you must specify a signer, current timestamp, release digest, and pull spec to sign")
}

sig := &signature{
Critical: criticalSignature{
Type: "atomic container signature",
Image: criticalImage{
DockerManifestDigest: releaseDigest,
},
Identity: criticalIdentity{
DockerReference: pullSpec,
},
},
Optional: optionalSignature{
Creator: signer,
Timestamp: now.Unix(),
},
}
return json.MarshalIndent(sig, "", " ")
}

// An atomic container signature has the following schema:
//
// {
// "critical": {
// "type": "atomic container signature",
// "image": {
// "docker-manifest-digest": "sha256:817a12c32a39bbe394944ba49de563e085f1d3c5266eb8e9723256bc4448680e"
// },
// "identity": {
// "docker-reference": "docker.io/library/busybox:latest"
// }
// },
// "optional": {
// "creator": "some software package v1.0.1-35",
// "timestamp": 1483228800,
// }
// }
type signature struct {
Critical criticalSignature `json:"critical"`
Optional optionalSignature `json:"optional"`
}

type criticalSignature struct {
Type string `json:"type"`
Image criticalImage `json:"image"`
Identity criticalIdentity `json:"identity"`
}

type criticalImage struct {
DockerManifestDigest string `json:"docker-manifest-digest"`
}

type criticalIdentity struct {
DockerReference string `json:"docker-reference"`
}

type optionalSignature struct {
Creator string `json:"creator"`
Timestamp int64 `json:"timestamp"`
}

0 comments on commit 2522ba4

Please sign in to comment.