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  }