Check push permissions before building images (#622)
* Check push permissions before building images * Fix doc comment * improve error messages
This commit is contained in:
parent
28bfb75a31
commit
3fa411ceb9
|
|
@ -430,7 +430,7 @@
|
||||||
version = "v0.2.0"
|
version = "v0.2.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3edac9d0a5f7e0e636f85bd7d3105df6180af528ab7e6a88f00b1ae6fc0bf947"
|
digest = "1:d40a26f0daf07f3b5c916356a3e10fabbf97d5166f77e57aa3983013ab57004c"
|
||||||
name = "github.com/google/go-containerregistry"
|
name = "github.com/google/go-containerregistry"
|
||||||
packages = [
|
packages = [
|
||||||
"pkg/authn",
|
"pkg/authn",
|
||||||
|
|
@ -450,7 +450,7 @@
|
||||||
"pkg/v1/v1util",
|
"pkg/v1/v1util",
|
||||||
]
|
]
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "8c1640add99804503b4126abc718931a4d93c31a"
|
revision = "8621d738a07bc74b2adeafd175a3c738423577a0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:f4f203acd8b11b8747bdcd91696a01dbc95ccb9e2ca2db6abf81c3a4f5e950ce"
|
digest = "1:f4f203acd8b11b8747bdcd91696a01dbc95ccb9e2ca2db6abf81c3a4f5e950ce"
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ required = [
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/google/go-containerregistry"
|
name = "github.com/google/go-containerregistry"
|
||||||
revision = "8c1640add99804503b4126abc718931a4d93c31a"
|
revision = "8621d738a07bc74b2adeafd175a3c738423577a0"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "k8s.io/apimachinery"
|
name = "k8s.io/apimachinery"
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/buildcontext"
|
"github.com/GoogleContainerTools/kaniko/pkg/buildcontext"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
"github.com/GoogleContainerTools/kaniko/pkg/config"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
"github.com/GoogleContainerTools/kaniko/pkg/constants"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/executor"
|
"github.com/GoogleContainerTools/kaniko/pkg/executor"
|
||||||
|
"github.com/GoogleContainerTools/kaniko/pkg/timing"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
||||||
"github.com/genuinetools/amicontained/container"
|
"github.com/genuinetools/amicontained/container"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -79,6 +78,9 @@ var RootCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
logrus.Warn("kaniko is being run outside of a container. This can have dangerous effects on your system")
|
logrus.Warn("kaniko is being run outside of a container. This can have dangerous effects on your system")
|
||||||
}
|
}
|
||||||
|
if err := executor.CheckPushPermissions(opts); err != nil {
|
||||||
|
exit(errors.Wrap(err, "error checking push permissions -- make sure you entered the correct tag name, and that you are authenticated correctly, and try again"))
|
||||||
|
}
|
||||||
if err := os.Chdir("/"); err != nil {
|
if err := os.Chdir("/"); err != nil {
|
||||||
exit(errors.Wrap(err, "error changing to root dir"))
|
exit(errors.Wrap(err, "error changing to root dir"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,30 @@ func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
return w.t.RoundTrip(r)
|
return w.t.RoundTrip(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckPushPermissionos checks that the configured credentials can be used to
|
||||||
|
// push to every specified destination.
|
||||||
|
func CheckPushPermissions(opts *config.KanikoOptions) error {
|
||||||
|
if opts.NoPush {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
checked := map[string]bool{}
|
||||||
|
for _, destination := range opts.Destinations {
|
||||||
|
destRef, err := name.NewTag(destination, name.WeakValidation)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting tag for destination")
|
||||||
|
}
|
||||||
|
if checked[destRef.Context().RepositoryStr()] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := remote.CheckPushPermission(destRef, creds.GetKeychain(), http.DefaultTransport); err != nil {
|
||||||
|
return errors.Wrapf(err, "checking push permission for %q", destRef)
|
||||||
|
}
|
||||||
|
checked[destRef.Context().RepositoryStr()] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DoPush is responsible for pushing image to the destinations specified in opts
|
// DoPush is responsible for pushing image to the destinations specified in opts
|
||||||
func DoPush(image v1.Image, opts *config.KanikoOptions) error {
|
func DoPush(image v1.Image, opts *config.KanikoOptions) error {
|
||||||
t := timing.Start("Total Push Time")
|
t := timing.Start("Total Push Time")
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
|
|
||||||
// Manifest represents the OCI image manifest in a structured way.
|
// Manifest represents the OCI image manifest in a structured way.
|
||||||
type Manifest struct {
|
type Manifest struct {
|
||||||
SchemaVersion int64 `json:"schemaVersion"`
|
SchemaVersion int64 `json:"schemaVersion,omitempty"`
|
||||||
MediaType types.MediaType `json:"mediaType"`
|
MediaType types.MediaType `json:"mediaType"`
|
||||||
Config Descriptor `json:"config"`
|
Config Descriptor `json:"config"`
|
||||||
Layers []Descriptor `json:"layers"`
|
Layers []Descriptor `json:"layers"`
|
||||||
|
|
|
||||||
56
vendor/github.com/google/go-containerregistry/pkg/v1/remote/check.go
generated
vendored
Normal file
56
vendor/github.com/google/go-containerregistry/pkg/v1/remote/check.go
generated
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckPushPermission returns an error if the given keychain cannot authorize
|
||||||
|
// a push operation to the given ref.
|
||||||
|
//
|
||||||
|
// This can be useful to check whether the caller has permission to push an
|
||||||
|
// image before doing work to construct the image.
|
||||||
|
//
|
||||||
|
// TODO(#412): Remove the need for this method.
|
||||||
|
func CheckPushPermission(ref name.Reference, kc authn.Keychain, t http.RoundTripper) error {
|
||||||
|
auth, err := kc.Resolve(ref.Context().Registry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scopes := []string{ref.Scope(transport.PushScope)}
|
||||||
|
tr, err := transport.New(ref.Context().Registry, auth, t, scopes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO(jasonhall): Against GCR, just doing the token handshake is
|
||||||
|
// enough, but this doesn't extend to Dockerhub
|
||||||
|
// (https://github.com/docker/hub-feedback/issues/1771), so we actually
|
||||||
|
// need to initiate an upload to tell whether the credentials can
|
||||||
|
// authorize a push. Figure out how to return early here when we can,
|
||||||
|
// to avoid a roundtrip for spec-compliant registries.
|
||||||
|
w := writer{
|
||||||
|
ref: ref,
|
||||||
|
client: &http.Client{Transport: tr},
|
||||||
|
}
|
||||||
|
loc, _, err := w.initiateUpload("", "")
|
||||||
|
if loc != "" {
|
||||||
|
// Since we're only initiating the upload to check whether we
|
||||||
|
// can, we should attempt to cancel it, in case initiating
|
||||||
|
// reserves some resources on the server. We shouldn't wait for
|
||||||
|
// cancelling to complete, and we don't care if it fails.
|
||||||
|
go w.cancelUpload(loc)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) cancelUpload(loc string) {
|
||||||
|
req, err := http.NewRequest(http.MethodDelete, loc, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = w.client.Do(req)
|
||||||
|
}
|
||||||
|
|
@ -28,31 +28,41 @@ import (
|
||||||
|
|
||||||
// WriteToFile writes in the compressed format to a tarball, on disk.
|
// WriteToFile writes in the compressed format to a tarball, on disk.
|
||||||
// This is just syntactic sugar wrapping tarball.Write with a new file.
|
// This is just syntactic sugar wrapping tarball.Write with a new file.
|
||||||
func WriteToFile(p string, tag name.Tag, img v1.Image) error {
|
func WriteToFile(p string, ref name.Reference, img v1.Image) error {
|
||||||
w, err := os.Create(p)
|
w, err := os.Create(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
return Write(tag, img, w)
|
return Write(ref, img, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiWriteToFile writes in the compressed format to a tarball, on disk.
|
// MultiWriteToFile writes in the compressed format to a tarball, on disk.
|
||||||
// This is just syntactic sugar wrapping tarball.MultiWrite with a new file.
|
// This is just syntactic sugar wrapping tarball.MultiWrite with a new file.
|
||||||
func MultiWriteToFile(p string, tagToImage map[name.Tag]v1.Image) error {
|
func MultiWriteToFile(p string, tagToImage map[name.Tag]v1.Image) error {
|
||||||
|
var refToImage map[name.Reference]v1.Image = make(map[name.Reference]v1.Image, len(tagToImage))
|
||||||
|
for i, d := range tagToImage {
|
||||||
|
refToImage[i] = d
|
||||||
|
}
|
||||||
|
return MultiRefWriteToFile(p, refToImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiRefWriteToFile writes in the compressed format to a tarball, on disk.
|
||||||
|
// This is just syntactic sugar wrapping tarball.MultiRefWrite with a new file.
|
||||||
|
func MultiRefWriteToFile(p string, refToImage map[name.Reference]v1.Image) error {
|
||||||
w, err := os.Create(p)
|
w, err := os.Create(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
return MultiWrite(tagToImage, w)
|
return MultiRefWrite(refToImage, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write is a wrapper to write a single image and tag to a tarball.
|
// Write is a wrapper to write a single image and tag to a tarball.
|
||||||
func Write(tag name.Tag, img v1.Image, w io.Writer) error {
|
func Write(ref name.Reference, img v1.Image, w io.Writer) error {
|
||||||
return MultiWrite(map[name.Tag]v1.Image{tag: img}, w)
|
return MultiRefWrite(map[name.Reference]v1.Image{ref: img}, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultiWrite writes the contents of each image to the provided reader, in the compressed format.
|
// MultiWrite writes the contents of each image to the provided reader, in the compressed format.
|
||||||
|
|
@ -61,10 +71,23 @@ func Write(tag name.Tag, img v1.Image, w io.Writer) error {
|
||||||
// One file for each layer, named after the layer's SHA.
|
// One file for each layer, named after the layer's SHA.
|
||||||
// One file for the config blob, named after its SHA.
|
// One file for the config blob, named after its SHA.
|
||||||
func MultiWrite(tagToImage map[name.Tag]v1.Image, w io.Writer) error {
|
func MultiWrite(tagToImage map[name.Tag]v1.Image, w io.Writer) error {
|
||||||
|
var refToImage map[name.Reference]v1.Image = make(map[name.Reference]v1.Image, len(tagToImage))
|
||||||
|
for i, d := range tagToImage {
|
||||||
|
refToImage[i] = d
|
||||||
|
}
|
||||||
|
return MultiRefWrite(refToImage, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiRefWrite writes the contents of each image to the provided reader, in the compressed format.
|
||||||
|
// The contents are written in the following format:
|
||||||
|
// One manifest.json file at the top level containing information about several images.
|
||||||
|
// One file for each layer, named after the layer's SHA.
|
||||||
|
// One file for the config blob, named after its SHA.
|
||||||
|
func MultiRefWrite(refToImage map[name.Reference]v1.Image, w io.Writer) error {
|
||||||
tf := tar.NewWriter(w)
|
tf := tar.NewWriter(w)
|
||||||
defer tf.Close()
|
defer tf.Close()
|
||||||
|
|
||||||
imageToTags := dedupTagToImage(tagToImage)
|
imageToTags := dedupRefToImage(refToImage)
|
||||||
var td tarDescriptor
|
var td tarDescriptor
|
||||||
|
|
||||||
for img, tags := range imageToTags {
|
for img, tags := range imageToTags {
|
||||||
|
|
@ -135,14 +158,20 @@ func MultiWrite(tagToImage map[name.Tag]v1.Image, w io.Writer) error {
|
||||||
return writeTarEntry(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
|
return writeTarEntry(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func dedupTagToImage(tagToImage map[name.Tag]v1.Image) map[v1.Image][]string {
|
func dedupRefToImage(refToImage map[name.Reference]v1.Image) map[v1.Image][]string {
|
||||||
imageToTags := make(map[v1.Image][]string)
|
imageToTags := make(map[v1.Image][]string)
|
||||||
|
|
||||||
for tag, img := range tagToImage {
|
for ref, img := range refToImage {
|
||||||
if tags, ok := imageToTags[img]; ok {
|
if tag, ok := ref.(name.Tag); ok {
|
||||||
imageToTags[img] = append(tags, tag.String())
|
if tags, ok := imageToTags[img]; ok && tags != nil {
|
||||||
|
imageToTags[img] = append(tags, tag.String())
|
||||||
|
} else {
|
||||||
|
imageToTags[img] = []string{tag.String()}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
imageToTags[img] = []string{tag.String()}
|
if _, ok := imageToTags[img]; !ok {
|
||||||
|
imageToTags[img] = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue