github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/image/daemon/podman.go (about) 1 package daemon 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "os" 11 "path/filepath" 12 13 api "github.com/docker/docker/api/types" 14 dimage "github.com/docker/docker/api/types/image" 15 "golang.org/x/xerrors" 16 ) 17 18 var ( 19 inspectURL = "http://podman/images/%s/json" 20 historyURL = "http://podman/images/%s/history" 21 saveURL = "http://podman/images/%s/get" 22 ) 23 24 type podmanClient struct { 25 c http.Client 26 } 27 28 func newPodmanClient() (podmanClient, error) { 29 // Get Podman socket location 30 sockDir := os.Getenv("XDG_RUNTIME_DIR") 31 socket := filepath.Join(sockDir, "podman", "podman.sock") 32 33 if _, err := os.Stat(socket); err != nil { 34 return podmanClient{}, xerrors.Errorf("no podman socket found: %w", err) 35 } 36 37 return podmanClient{ 38 c: http.Client{ 39 Transport: &http.Transport{ 40 DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { 41 return net.Dial("unix", socket) 42 }, 43 }, 44 }, 45 }, nil 46 } 47 48 type errResponse struct { 49 Message string 50 } 51 52 func (p podmanClient) imageInspect(imageName string) (api.ImageInspect, error) { 53 url := fmt.Sprintf(inspectURL, imageName) 54 resp, err := p.c.Get(url) 55 if err != nil { 56 return api.ImageInspect{}, xerrors.Errorf("http error: %w", err) 57 } 58 defer resp.Body.Close() 59 60 if resp.StatusCode != http.StatusOK { 61 var res errResponse 62 if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { 63 return api.ImageInspect{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode) 64 } 65 return api.ImageInspect{}, xerrors.New(res.Message) 66 } 67 68 var inspect api.ImageInspect 69 if err = json.NewDecoder(resp.Body).Decode(&inspect); err != nil { 70 return api.ImageInspect{}, xerrors.Errorf("unable to decode JSON: %w", err) 71 } 72 return inspect, nil 73 } 74 75 func (p podmanClient) imageHistoryInspect(imageName string) ([]dimage.HistoryResponseItem, error) { 76 url := fmt.Sprintf(historyURL, imageName) 77 resp, err := p.c.Get(url) 78 if err != nil { 79 return []dimage.HistoryResponseItem{}, xerrors.Errorf("http error: %w", err) 80 } 81 defer resp.Body.Close() 82 83 if resp.StatusCode != http.StatusOK { 84 var res errResponse 85 if err = json.NewDecoder(resp.Body).Decode(&res); err != nil { 86 return []dimage.HistoryResponseItem{}, xerrors.Errorf("unknown status code from Podman: %d", resp.StatusCode) 87 } 88 return []dimage.HistoryResponseItem{}, xerrors.New(res.Message) 89 } 90 91 var history []dimage.HistoryResponseItem 92 if err = json.NewDecoder(resp.Body).Decode(&history); err != nil { 93 return []dimage.HistoryResponseItem{}, xerrors.Errorf("unable to decode JSON: %w", err) 94 } 95 return history, nil 96 } 97 98 func (p podmanClient) imageSave(_ context.Context, imageNames []string) (io.ReadCloser, error) { 99 if len(imageNames) < 1 { 100 return nil, xerrors.Errorf("no specified image") 101 } 102 url := fmt.Sprintf(saveURL, imageNames[0]) 103 resp, err := p.c.Get(url) 104 if err != nil { 105 return nil, xerrors.Errorf("http error: %w", err) 106 } 107 return resp.Body, nil 108 } 109 110 // PodmanImage implements v1.Image by extending daemon.Image. 111 // The caller must call cleanup() to remove a temporary file. 112 func PodmanImage(ref string) (Image, func(), error) { 113 cleanup := func() {} 114 115 c, err := newPodmanClient() 116 if err != nil { 117 return nil, cleanup, xerrors.Errorf("unable to initialize Podman client: %w", err) 118 } 119 inspect, err := c.imageInspect(ref) 120 if err != nil { 121 return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err) 122 } 123 124 history, err := c.imageHistoryInspect(ref) 125 if err != nil { 126 return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err) 127 } 128 129 f, err := os.CreateTemp("", "fanal-*") 130 if err != nil { 131 return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) 132 } 133 134 cleanup = func() { 135 _ = f.Close() 136 _ = os.Remove(f.Name()) 137 } 138 139 return &image{ 140 opener: imageOpener(context.Background(), ref, f, c.imageSave), 141 inspect: inspect, 142 history: configHistory(history), 143 }, cleanup, nil 144 }