@@ -14,6 +14,7 @@ import (
1414
1515 "sigs.k8s.io/kustomize/api/ifc"
1616 "sigs.k8s.io/kustomize/api/internal/git"
17+ "sigs.k8s.io/kustomize/api/internal/oci"
1718 "sigs.k8s.io/kustomize/kyaml/errors"
1819 "sigs.k8s.io/kustomize/kyaml/filesys"
1920)
@@ -93,6 +94,10 @@ type FileLoader struct {
9394 // obtained from the given repository.
9495 repoSpec * git.RepoSpec
9596
97+ // If this is non-nil, the files were
98+ // obtained from the given oci repository.
99+ ociSpec * oci.RepoSpec
100+
96101 // File system utilities.
97102 fSys filesys.FileSystem
98103
@@ -102,6 +107,9 @@ type FileLoader struct {
102107 // Used to clone repositories.
103108 cloner git.Cloner
104109
110+ // Used to pull registry images
111+ puller oci.Puller
112+
105113 // Used to clean up, as needed.
106114 cleaner func () error
107115}
@@ -112,6 +120,9 @@ func (fl *FileLoader) Repo() string {
112120 if fl .repoSpec != nil {
113121 return fl .repoSpec .Dir .String ()
114122 }
123+ if fl .ociSpec != nil {
124+ return fl .ociSpec .Dir .String ()
125+ }
115126 return ""
116127}
117128
@@ -129,20 +140,21 @@ func NewLoaderOrDie(
129140 log .Fatalf ("unable to make loader at '%s'; %v" , path , err )
130141 }
131142 return newLoaderAtConfirmedDir (
132- lr , root , fSys , nil , git .ClonerUsingGitExec )
143+ lr , root , fSys , nil , git .ClonerUsingGitExec , nil )
133144}
134145
135146// newLoaderAtConfirmedDir returns a new FileLoader with given root.
136147func newLoaderAtConfirmedDir (
137148 lr LoadRestrictorFunc ,
138149 root filesys.ConfirmedDir , fSys filesys.FileSystem ,
139- referrer * FileLoader , cloner git.Cloner ) * FileLoader {
150+ referrer * FileLoader , cloner git.Cloner , puller oci. Puller ) * FileLoader {
140151 return & FileLoader {
141152 loadRestrictor : lr ,
142153 root : root ,
143154 referrer : referrer ,
144155 fSys : fSys ,
145156 cloner : cloner ,
157+ puller : puller ,
146158 cleaner : func () error { return nil },
147159 }
148160}
@@ -184,6 +196,16 @@ func (fl *FileLoader) New(path string) (ifc.Loader, error) {
184196 repoSpec , fl .fSys , fl , fl .cloner )
185197 }
186198
199+ ociSpec , err := oci .NewRepoSpecFromURL (path )
200+ if err == nil {
201+ // Treat this as git repo clone request.
202+ if err = fl .errIfOciRepoCycle (ociSpec ); err != nil {
203+ return nil , err
204+ }
205+ return newLoaderAtOciPull (
206+ ociSpec , fl .fSys , fl , fl .puller )
207+ }
208+
187209 if filepath .IsAbs (path ) {
188210 return nil , fmt .Errorf ("new root '%s' cannot be absolute" , path )
189211 }
@@ -201,11 +223,14 @@ func (fl *FileLoader) New(path string) (ifc.Loader, error) {
201223 if err = fl .errIfGitContainmentViolation (root ); err != nil {
202224 return nil , err
203225 }
226+ if err = fl .errIfOciContainmentViolation (root ); err != nil {
227+ return nil , err
228+ }
204229 if err = fl .errIfArgEqualOrHigher (root ); err != nil {
205230 return nil , err
206231 }
207232 return newLoaderAtConfirmedDir (
208- fl .loadRestrictor , root , fl .fSys , fl , fl .cloner ), nil
233+ fl .loadRestrictor , root , fl .fSys , fl , fl .cloner , fl . puller ), nil
209234}
210235
211236// newLoaderAtGitClone returns a new Loader pinned to a temporary
@@ -253,6 +278,51 @@ func newLoaderAtGitClone(
253278 }, nil
254279}
255280
281+ // newLoaderAtOciPull returns a new Loader pinned to a temporary
282+ // directory holding a pulled oci artifact.
283+ func newLoaderAtOciPull (
284+ repoSpec * oci.RepoSpec , fSys filesys.FileSystem ,
285+ referrer * FileLoader , puller oci.Puller ) (ifc.Loader , error ) {
286+ cleaner := repoSpec .Cleaner (fSys )
287+ err := puller (repoSpec )
288+ if err != nil {
289+ cleaner ()
290+ return nil , err
291+ }
292+ root , f , err := fSys .CleanedAbs (repoSpec .AbsPath ())
293+ if err != nil {
294+ cleaner ()
295+ return nil , err
296+ }
297+ // We don't know that the path requested in repoSpec
298+ // is a directory until we actually clone it and look
299+ // inside. That just happened, hence the error check
300+ // is here.
301+ if f != "" {
302+ cleaner ()
303+ return nil , fmt .Errorf (
304+ "'%s' refers to file '%s'; expecting directory" ,
305+ repoSpec .AbsPath (), f )
306+ }
307+ // Path in repo can contain symlinks that exit repo. We can only
308+ // check for this after cloning repo.
309+ if ! root .HasPrefix (repoSpec .PullDir ()) {
310+ _ = cleaner ()
311+ return nil , fmt .Errorf ("%q refers to directory outside of repo %q" , repoSpec .AbsPath (),
312+ repoSpec .PullDir ())
313+ }
314+ return & FileLoader {
315+ // Pulls never allowed to escape root.
316+ loadRestrictor : RestrictionRootOnly ,
317+ root : root ,
318+ referrer : referrer ,
319+ ociSpec : repoSpec ,
320+ fSys : fSys ,
321+ puller : puller ,
322+ cleaner : cleaner ,
323+ }, nil
324+ }
325+
256326func (fl * FileLoader ) errIfGitContainmentViolation (
257327 base filesys.ConfirmedDir ) error {
258328 containingRepo := fl .containingRepo ()
@@ -269,6 +339,22 @@ func (fl *FileLoader) errIfGitContainmentViolation(
269339 return nil
270340}
271341
342+ func (fl * FileLoader ) errIfOciContainmentViolation (
343+ base filesys.ConfirmedDir ) error {
344+ containingRepo := fl .containingOciRepo ()
345+ if containingRepo == nil {
346+ return nil
347+ }
348+ if ! base .HasPrefix (containingRepo .PullDir ()) {
349+ return fmt .Errorf (
350+ "security; bases in kustomizations found in " +
351+ "pulled oci repos must be within the repo, " +
352+ "but base '%s' is outside '%s'" ,
353+ base , containingRepo .PullDir ())
354+ }
355+ return nil
356+ }
357+
272358// Looks back through referrers for a git repo, returning nil
273359// if none found.
274360func (fl * FileLoader ) containingRepo () * git.RepoSpec {
@@ -281,6 +367,18 @@ func (fl *FileLoader) containingRepo() *git.RepoSpec {
281367 return fl .referrer .containingRepo ()
282368}
283369
370+ // Looks back through referrers for an oci repo, returning nil
371+ // if none found.
372+ func (fl * FileLoader ) containingOciRepo () * oci.RepoSpec {
373+ if fl .ociSpec != nil {
374+ return fl .ociSpec
375+ }
376+ if fl .referrer == nil {
377+ return nil
378+ }
379+ return fl .referrer .containingOciRepo ()
380+ }
381+
284382// errIfArgEqualOrHigher tests whether the argument,
285383// is equal to or above the root of any ancestor.
286384func (fl * FileLoader ) errIfArgEqualOrHigher (
@@ -314,6 +412,24 @@ func (fl *FileLoader) errIfRepoCycle(newRepoSpec *git.RepoSpec) error {
314412 return fl .referrer .errIfRepoCycle (newRepoSpec )
315413}
316414
415+ // TODO(monopole): Distinguish tags/digests?
416+ // I.e. Allow a distinction between oci artifacts with
417+ // path foo and tag bar and a URI with the same
418+ // path but a different tag/digest?
419+ func (fl * FileLoader ) errIfOciRepoCycle (newRepoSpec * oci.RepoSpec ) error {
420+ // TODO(monopole): Use parsed data instead of Raw().
421+ if fl .ociSpec != nil &&
422+ strings .HasPrefix (fl .ociSpec .Raw (), newRepoSpec .Raw ()) {
423+ return fmt .Errorf (
424+ "cycle detected: URI '%s' referenced by previous URI '%s'" ,
425+ newRepoSpec .Raw (), fl .ociSpec .Raw ())
426+ }
427+ if fl .referrer == nil {
428+ return nil
429+ }
430+ return fl .referrer .errIfOciRepoCycle (newRepoSpec )
431+ }
432+
317433// Load returns the content of file at the given path,
318434// else an error. Relative paths are taken relative
319435// to the root.
0 commit comments