diff --git a/cmd/metal-api/internal/service/image-service.go b/cmd/metal-api/internal/service/image-service.go index a647d569..a26f81f1 100644 --- a/cmd/metal-api/internal/service/image-service.go +++ b/cmd/metal-api/internal/service/image-service.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "net/http" + "net/url" "strconv" "time" @@ -16,6 +17,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,7 +321,7 @@ func (r *imageResource) createImage(request *restful.Request, response *restful. } } - err = checkImageURL(requestPayload.ID, requestPayload.URL) + err = checkImageURL(requestPayload.ID, requestPayload.URL, nil) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return @@ -346,15 +350,39 @@ func (r *imageResource) createImage(request *restful.Request, response *restful. r.send(request, response, http.StatusCreated, v1.NewImageResponse(img)) } -func checkImageURL(id, url string) error { - // nolint - res, err := http.Head(url) +func checkImageURL(id, inputURL string, ociCredentials authn.Authenticator) error { + parsedURL, err := url.Parse(inputURL) if err != nil { - return fmt.Errorf("image:%s is not accessible under:%s error:%w", id, url, err) + return fmt.Errorf("image:%s with url:%s could not be parsed. error:%w", id, inputURL, err) } - if res.StatusCode >= 400 { - return fmt.Errorf("image:%s is not accessible under:%s status:%s", id, url, res.Status) + + switch parsedURL.Scheme { + case "http", "https": + res, err := http.Head(inputURL) + if err != nil { + return fmt.Errorf("image:%s is not accessible under:%s error:%w", id, inputURL, err) + } + if res.StatusCode >= 400 { + return fmt.Errorf("image:%s is not accessible under:%s status:%s", id, inputURL, res.Status) + } + case "oci": + ref, err := name.ParseReference(inputURL) + if err != nil { + return fmt.Errorf("image reference:%s could not be parsed. error:%w", inputURL, err) + } + + if ociCredentials == nil { + ociCredentials = authn.Anonymous + } + + _, err = remote.Head(ref, remote.WithAuth(ociCredentials)) + if err != nil { + return fmt.Errorf("image:%s is not accessible under:%s error:%w", id, inputURL, err) + } + default: + return fmt.Errorf("image:%s with url:%s has unkown protocol. error:%w", id, inputURL, err) } + return nil } @@ -411,7 +439,7 @@ func (r *imageResource) updateImage(request *restful.Request, response *restful. newImage.Description = *requestPayload.Description } if requestPayload.URL != nil { - err = checkImageURL(requestPayload.ID, *requestPayload.URL) + err = checkImageURL(requestPayload.ID, *requestPayload.URL, nil) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return diff --git a/cmd/metal-api/internal/service/partition-service.go b/cmd/metal-api/internal/service/partition-service.go index 9eee4531..54ea9a96 100644 --- a/cmd/metal-api/internal/service/partition-service.go +++ b/cmd/metal-api/internal/service/partition-service.go @@ -175,7 +175,7 @@ func (r *partitionResource) createPartition(request *restful.Request, response * imageURL = *requestPayload.PartitionBootConfiguration.ImageURL } - err = checkImageURL("image", imageURL) + err = checkImageURL("image", imageURL, nil) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return @@ -186,7 +186,7 @@ func (r *partitionResource) createPartition(request *restful.Request, response * kernelURL = *requestPayload.PartitionBootConfiguration.KernelURL } - err = checkImageURL("kernel", kernelURL) + err = checkImageURL("kernel", kernelURL, nil) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return @@ -275,7 +275,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 + ) + err := request.ReadEntity(&requestPayload) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) @@ -303,7 +306,7 @@ func (r *partitionResource) updatePartition(request *restful.Request, response * newPartition.Labels = requestPayload.Labels } if requestPayload.PartitionBootConfiguration.ImageURL != nil { - err = checkImageURL("image", *requestPayload.PartitionBootConfiguration.ImageURL) + err = checkImageURL("image", *requestPayload.PartitionBootConfiguration.ImageURL, nil) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return @@ -313,11 +316,12 @@ func (r *partitionResource) updatePartition(request *restful.Request, response * } if requestPayload.PartitionBootConfiguration.KernelURL != nil { - err = checkImageURL("kernel", *requestPayload.PartitionBootConfiguration.KernelURL) + err = checkImageURL("kernel", *requestPayload.PartitionBootConfiguration.KernelURL, nil) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return } + newPartition.BootConfiguration.KernelURL = *requestPayload.PartitionBootConfiguration.KernelURL } if requestPayload.PartitionBootConfiguration.CommandLine != nil { diff --git a/go.mod b/go.mod index 99411d6f..3027d192 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/emicklei/go-restful/v3 v3.13.0 github.com/go-openapi/spec v0.22.1 github.com/google/go-cmp v0.7.0 + github.com/google/go-containerregistry v0.20.7 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 @@ -55,6 +56,7 @@ require ( 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/containerd/stargz-snapshotter/estargz v0.18.1 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect @@ -63,7 +65,10 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v29.0.3+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ebitengine/purego v0.8.4 // indirect @@ -123,6 +128,7 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/minio/minlz v1.0.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect @@ -165,6 +171,7 @@ require ( github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect + github.com/vbatts/tar-split v0.12.2 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // 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=