github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/embeddedfs/ova/ova.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package ova provides an extractor for extracting software inventories from OVA archives 16 package ova 17 18 import ( 19 "context" 20 "errors" 21 "fmt" 22 "strings" 23 "sync" 24 25 cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" 26 "github.com/google/osv-scalibr/extractor/filesystem" 27 "github.com/google/osv-scalibr/extractor/filesystem/embeddedfs/common" 28 scalibrfs "github.com/google/osv-scalibr/fs" 29 "github.com/google/osv-scalibr/inventory" 30 "github.com/google/osv-scalibr/plugin" 31 ) 32 33 const ( 34 // Name is the unique identifier for the ova extractor. 35 Name = "embeddedfs/ova" 36 ) 37 38 // Extractor implements the filesystem.Extractor interface for ova. 39 type Extractor struct { 40 // maxFileSizeBytes is the maximum size of an archive file that can be traversed. 41 // If this limit is greater than zero and a file is encountered that is larger 42 // than this limit, the file is ignored. 43 maxFileSizeBytes int64 44 } 45 46 // New returns a new ova extractor. 47 func New(cfg *cpb.PluginConfig) filesystem.Extractor { 48 maxSize := cfg.MaxFileSizeBytes 49 specific := plugin.FindConfig(cfg, func(c *cpb.PluginSpecificConfig) *cpb.OVAConfig { return c.GetOva() }) 50 if specific.GetMaxFileSizeBytes() > 0 { 51 maxSize = specific.GetMaxFileSizeBytes() 52 } 53 return &Extractor{maxFileSizeBytes: maxSize} 54 } 55 56 // Name returns the name of the extractor. 57 func (e *Extractor) Name() string { 58 return Name 59 } 60 61 // Version returns the version of the extractor. 62 func (e *Extractor) Version() int { 63 return 0 64 } 65 66 // Requirements returns the requirements for the extractor. 67 func (e *Extractor) Requirements() *plugin.Capabilities { 68 return &plugin.Capabilities{} 69 } 70 71 // FileRequired checks if the file is a .ova file based on its extension. 72 func (e *Extractor) FileRequired(api filesystem.FileAPI) bool { 73 path := api.Path() 74 if !strings.HasSuffix(strings.ToLower(path), ".ova") { 75 return false 76 } 77 78 fileinfo, err := api.Stat() 79 if err != nil { 80 return false 81 } 82 83 if e.maxFileSizeBytes > 0 && fileinfo.Size() > e.maxFileSizeBytes { 84 return false 85 } 86 87 return true 88 } 89 90 // Extract returns an Inventory with embedded filesystems which contains a mount function for the filesystem in the .ova file. 91 func (e *Extractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) { 92 // Check wether input.Reader is nil or not. 93 // This check is crucial because tar.NewReader doesn't validate the input, 94 // it simply wraps it around tar.Reader. 95 if input.Reader == nil { 96 return inventory.Inventory{}, errors.New("input.Reader is nil") 97 } 98 99 tempDir, err := common.TARToTempDir(input.Reader) 100 if err != nil { 101 return inventory.Inventory{}, fmt.Errorf("common.TARToTempDir(%q): %w", input.Path, err) 102 } 103 104 var refCount int32 = 1 105 var refMu sync.Mutex 106 getEmbeddedFS := func(ctx context.Context) (scalibrfs.FS, error) { 107 return &common.EmbeddedDirFS{ 108 FS: scalibrfs.DirFS(tempDir), 109 File: nil, 110 TmpPaths: []string{tempDir}, 111 RefCount: &refCount, 112 RefMu: &refMu, 113 }, nil 114 } 115 var inv inventory.Inventory 116 inv.EmbeddedFSs = append(inv.EmbeddedFSs, &inventory.EmbeddedFS{ 117 Path: input.Path, 118 GetEmbeddedFS: getEmbeddedFS, 119 }) 120 return inv, nil 121 }