diff --git a/cmd/metal-api/internal/service/image-service.go b/cmd/metal-api/internal/service/image-service.go index a647d569..5041c500 100644 --- a/cmd/metal-api/internal/service/image-service.go +++ b/cmd/metal-api/internal/service/image-service.go @@ -16,6 +16,9 @@ import ( restfulspec "github.com/emicklei/go-restful-openapi/v2" restful "github.com/emicklei/go-restful/v3" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/metal-stack/metal-lib/httperrors" ) @@ -317,10 +320,16 @@ func (r *imageResource) createImage(request *restful.Request, response *restful. } } + var errs []error err = checkImageURL(requestPayload.ID, requestPayload.URL) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + errs = append(errs, err) + err = checkOciImageURI(requestPayload.ID, "", "", requestPayload.URL) + if err != nil { + errs = append(errs, err) + r.sendError(request, response, httperrors.BadRequest(errors.Join(errs...))) + return + } } img := &metal.Image{ @@ -346,15 +355,42 @@ func (r *imageResource) createImage(request *restful.Request, response *restful. r.send(request, response, http.StatusCreated, v1.NewImageResponse(img)) } +func checkOciImageURI(id, username, password, uri string) error { + ref, err := name.ParseReference(uri) + if err != nil { + return fmt.Errorf("image reference:%s could not be parsed. error:%w", uri, err) + } + + var auth = authn.Anonymous + if username != "" || password != "" { + auth = &authn.Basic{ + Username: username, + Password: password, + } + } + + _, err = remote.Head(ref, remote.WithAuth(auth)) + if err != nil { + return fmt.Errorf("image:%s is not accessible under:%s error:%w", id, uri, err) + } + + return nil +} + func checkImageURL(id, url string) error { - // nolint - res, err := http.Head(url) + var ( + err error + res *http.Response + ) + + res, err = http.Head(url) if err != nil { return fmt.Errorf("image:%s is not accessible under:%s error:%w", id, url, err) } if res.StatusCode >= 400 { return fmt.Errorf("image:%s is not accessible under:%s status:%s", id, url, res.Status) } + return nil } @@ -411,10 +447,16 @@ func (r *imageResource) updateImage(request *restful.Request, response *restful. newImage.Description = *requestPayload.Description } if requestPayload.URL != nil { + var errs []error err = checkImageURL(requestPayload.ID, *requestPayload.URL) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + errs = append(errs, err) + err = checkOciImageURI(requestPayload.ID, "", "", *requestPayload.URL) + if err != nil { + errs = append(errs, err) + r.sendError(request, response, httperrors.BadRequest(errors.Join(errs...))) + return + } } newImage.URL = *requestPayload.URL diff --git a/cmd/metal-api/internal/service/partition-service.go b/cmd/metal-api/internal/service/partition-service.go index 9eee4531..a065dd01 100644 --- a/cmd/metal-api/internal/service/partition-service.go +++ b/cmd/metal-api/internal/service/partition-service.go @@ -175,10 +175,16 @@ func (r *partitionResource) createPartition(request *restful.Request, response * imageURL = *requestPayload.PartitionBootConfiguration.ImageURL } + var errs []error err = checkImageURL("image", imageURL) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + errs = append(errs, err) + err = checkOciImageURI("image", "", "", imageURL) + if err != nil { + errs = append(errs, err) + r.sendError(request, response, httperrors.BadRequest(errors.Join(errs...))) + return + } } var kernelURL string @@ -186,10 +192,16 @@ func (r *partitionResource) createPartition(request *restful.Request, response * kernelURL = *requestPayload.PartitionBootConfiguration.KernelURL } - err = checkImageURL("kernel", kernelURL) + errs = []error{} + err = checkImageURL("kernel", imageURL) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + errs = append(errs, err) + err = checkOciImageURI("kernel", "", "", imageURL) + if err != nil { + errs = append(errs, err) + r.sendError(request, response, httperrors.BadRequest(errors.Join(errs...))) + return + } } var commandLine string @@ -275,7 +287,10 @@ func (r *partitionResource) deletePartition(request *restful.Request, response * } func (r *partitionResource) updatePartition(request *restful.Request, response *restful.Response) { - var requestPayload v1.PartitionUpdateRequest + var ( + requestPayload v1.PartitionUpdateRequest + errs []error + ) err := request.ReadEntity(&requestPayload) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) @@ -305,19 +320,31 @@ func (r *partitionResource) updatePartition(request *restful.Request, response * if requestPayload.PartitionBootConfiguration.ImageURL != nil { err = checkImageURL("image", *requestPayload.PartitionBootConfiguration.ImageURL) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + errs = append(errs, err) + err = checkOciImageURI("image", "", "", *requestPayload.PartitionBootConfiguration.ImageURL) + if err != nil { + errs = append(errs, err) + r.sendError(request, response, httperrors.BadRequest(errors.Join(errs...))) + return + } } newPartition.BootConfiguration.ImageURL = *requestPayload.PartitionBootConfiguration.ImageURL } if requestPayload.PartitionBootConfiguration.KernelURL != nil { + errs = []error{} err = checkImageURL("kernel", *requestPayload.PartitionBootConfiguration.KernelURL) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + errs = append(errs, err) + err = checkOciImageURI("kernel", "", "", *requestPayload.PartitionBootConfiguration.KernelURL) + if err != nil { + errs = append(errs, err) + r.sendError(request, response, httperrors.BadRequest(errors.Join(errs...))) + return + } } + newPartition.BootConfiguration.KernelURL = *requestPayload.PartitionBootConfiguration.KernelURL } if requestPayload.PartitionBootConfiguration.CommandLine != nil { diff --git a/go.mod b/go.mod index 99411d6f..ccd172f4 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,21 @@ require ( gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2 ) +require ( + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect +) + +require ( + github.com/containerd/stargz-snapshotter/estargz v0.18.1 // indirect + github.com/docker/cli v29.0.3+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/google/go-containerregistry v0.20.7 + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/vbatts/tar-split v0.12.2 // indirect +) + // Newer versions do not export base entities which are used to composite other entities. // This breaks metalctl and friends replace github.com/emicklei/go-restful-openapi/v2 => github.com/emicklei/go-restful-openapi/v2 v2.9.1 @@ -51,8 +66,6 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect diff --git a/go.sum b/go.sum index d35024db..3493159f 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8= +github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= @@ -71,8 +73,14 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v29.0.3+incompatible h1:8J+PZIcF2xLd6h5sHPsp5pvvJA+Sr2wGQxHkRl53a1E= +github.com/docker/cli v29.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -180,6 +188,8 @@ github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25 h1:sEDPKUw6iPjczdu33njxFjO6tYa9bfc0z/QyB/zSsBw= github.com/google/pprof v0.0.0-20240829160300-da1f7e9f2b25/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= @@ -300,6 +310,8 @@ github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= github.com/metal-stack/v v1.0.3/go.mod h1:YTahEu7/ishwpYKnp/VaW/7nf8+PInogkfGwLcGPdXg= github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= @@ -428,6 +440,8 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=