github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/trust/trust.go (about)

     1  package trust
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/containers/image/v5/types"
    15  	"github.com/ghodss/yaml"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  // PolicyContent struct for policy.json file
    21  type PolicyContent struct {
    22  	Default    []RepoContent     `json:"default"`
    23  	Transports TransportsContent `json:"transports"`
    24  }
    25  
    26  // RepoContent struct used under each repo
    27  type RepoContent struct {
    28  	Type           string          `json:"type"`
    29  	KeyType        string          `json:"keyType,omitempty"`
    30  	KeyPath        string          `json:"keyPath,omitempty"`
    31  	KeyData        string          `json:"keyData,omitempty"`
    32  	SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"`
    33  }
    34  
    35  // RepoMap map repo name to policycontent for each repo
    36  type RepoMap map[string][]RepoContent
    37  
    38  // TransportsContent struct for content under "transports"
    39  type TransportsContent map[string]RepoMap
    40  
    41  // RegistryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all.
    42  // NOTE: Keep this in sync with docs/registries.d.md!
    43  type RegistryConfiguration struct {
    44  	DefaultDocker *RegistryNamespace `json:"default-docker"`
    45  	// The key is a namespace, using fully-expanded Docker reference format or parent namespaces (per dockerReference.PolicyConfiguration*),
    46  	Docker map[string]RegistryNamespace `json:"docker"`
    47  }
    48  
    49  // RegistryNamespace defines lookaside locations for a single namespace.
    50  type RegistryNamespace struct {
    51  	SigStore        string `json:"sigstore"`         // For reading, and if SigStoreStaging is not present, for writing.
    52  	SigStoreStaging string `json:"sigstore-staging"` // For writing only.
    53  }
    54  
    55  // ShowOutput keep the fields for image trust show command
    56  type ShowOutput struct {
    57  	Repo      string
    58  	Trusttype string
    59  	GPGid     string
    60  	Sigstore  string
    61  }
    62  
    63  // DefaultPolicyPath returns a path to the default policy of the system.
    64  func DefaultPolicyPath(sys *types.SystemContext) string {
    65  	systemDefaultPolicyPath := "/etc/containers/policy.json"
    66  	if sys != nil {
    67  		if sys.SignaturePolicyPath != "" {
    68  			return sys.SignaturePolicyPath
    69  		}
    70  		if sys.RootForImplicitAbsolutePaths != "" {
    71  			return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
    72  		}
    73  	}
    74  	return systemDefaultPolicyPath
    75  }
    76  
    77  // RegistriesDirPath returns a path to registries.d
    78  func RegistriesDirPath(sys *types.SystemContext) string {
    79  	systemRegistriesDirPath := "/etc/containers/registries.d"
    80  	if sys != nil {
    81  		if sys.RegistriesDirPath != "" {
    82  			return sys.RegistriesDirPath
    83  		}
    84  		if sys.RootForImplicitAbsolutePaths != "" {
    85  			return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
    86  		}
    87  	}
    88  	return systemRegistriesDirPath
    89  }
    90  
    91  // LoadAndMergeConfig loads configuration files in dirPath
    92  func LoadAndMergeConfig(dirPath string) (*RegistryConfiguration, error) {
    93  	mergedConfig := RegistryConfiguration{Docker: map[string]RegistryNamespace{}}
    94  	dockerDefaultMergedFrom := ""
    95  	nsMergedFrom := map[string]string{}
    96  
    97  	dir, err := os.Open(dirPath)
    98  	if err != nil {
    99  		if os.IsNotExist(err) {
   100  			return &mergedConfig, nil
   101  		}
   102  		return nil, err
   103  	}
   104  	configNames, err := dir.Readdirnames(0)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	for _, configName := range configNames {
   109  		if !strings.HasSuffix(configName, ".yaml") {
   110  			continue
   111  		}
   112  		configPath := filepath.Join(dirPath, configName)
   113  		configBytes, err := ioutil.ReadFile(configPath)
   114  		if err != nil {
   115  			return nil, err
   116  		}
   117  		var config RegistryConfiguration
   118  		err = yaml.Unmarshal(configBytes, &config)
   119  		if err != nil {
   120  			return nil, errors.Wrapf(err, "error parsing %s", configPath)
   121  		}
   122  		if config.DefaultDocker != nil {
   123  			if mergedConfig.DefaultDocker != nil {
   124  				return nil, errors.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
   125  					dockerDefaultMergedFrom, configPath)
   126  			}
   127  			mergedConfig.DefaultDocker = config.DefaultDocker
   128  			dockerDefaultMergedFrom = configPath
   129  		}
   130  		for nsName, nsConfig := range config.Docker { // includes config.Docker == nil
   131  			if _, ok := mergedConfig.Docker[nsName]; ok {
   132  				return nil, errors.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
   133  					nsName, nsMergedFrom[nsName], configPath)
   134  			}
   135  			mergedConfig.Docker[nsName] = nsConfig
   136  			nsMergedFrom[nsName] = configPath
   137  		}
   138  	}
   139  	return &mergedConfig, nil
   140  }
   141  
   142  // HaveMatchRegistry checks if trust settings for the registry have been configured in yaml file
   143  func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *RegistryNamespace {
   144  	searchKey := key
   145  	if !strings.Contains(searchKey, "/") {
   146  		val, exists := registryConfigs.Docker[searchKey]
   147  		if exists {
   148  			return &val
   149  		}
   150  	}
   151  	for range strings.Split(key, "/") {
   152  		val, exists := registryConfigs.Docker[searchKey]
   153  		if exists {
   154  			return &val
   155  		}
   156  		if strings.Contains(searchKey, "/") {
   157  			searchKey = searchKey[:strings.LastIndex(searchKey, "/")]
   158  		}
   159  	}
   160  	return registryConfigs.DefaultDocker
   161  }
   162  
   163  // CreateTmpFile creates a temp file under dir and writes the content into it
   164  func CreateTmpFile(dir, pattern string, content []byte) (string, error) {
   165  	tmpfile, err := ioutil.TempFile(dir, pattern)
   166  	if err != nil {
   167  		return "", err
   168  	}
   169  	defer tmpfile.Close()
   170  
   171  	if _, err := tmpfile.Write(content); err != nil {
   172  		return "", err
   173  
   174  	}
   175  	return tmpfile.Name(), nil
   176  }
   177  
   178  // GetGPGIdFromKeyPath return user keyring from key path
   179  func GetGPGIdFromKeyPath(path string) []string {
   180  	cmd := exec.Command("gpg2", "--with-colons", path)
   181  	results, err := cmd.Output()
   182  	if err != nil {
   183  		logrus.Errorf("error getting key identity: %s", err)
   184  		return nil
   185  	}
   186  	return parseUids(results)
   187  }
   188  
   189  // GetGPGIdFromKeyData return user keyring from keydata
   190  func GetGPGIdFromKeyData(key string) []string {
   191  	decodeKey, err := base64.StdEncoding.DecodeString(key)
   192  	if err != nil {
   193  		logrus.Errorf("%s, error decoding key data", err)
   194  		return nil
   195  	}
   196  	tmpfileName, err := CreateTmpFile("", "", decodeKey)
   197  	if err != nil {
   198  		logrus.Errorf("error creating key date temp file %s", err)
   199  	}
   200  	defer os.Remove(tmpfileName)
   201  	return GetGPGIdFromKeyPath(tmpfileName)
   202  }
   203  
   204  func parseUids(colonDelimitKeys []byte) []string {
   205  	var parseduids []string
   206  	scanner := bufio.NewScanner(bytes.NewReader(colonDelimitKeys))
   207  	for scanner.Scan() {
   208  		line := scanner.Text()
   209  		if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
   210  			uid := strings.Split(line, ":")[9]
   211  			if uid == "" {
   212  				continue
   213  			}
   214  			parseduid := uid
   215  			if strings.Contains(uid, "<") && strings.Contains(uid, ">") {
   216  				parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0]
   217  			}
   218  			parseduids = append(parseduids, parseduid)
   219  		}
   220  	}
   221  	return parseduids
   222  }
   223  
   224  // GetPolicy parse policy.json into PolicyContent struct
   225  func GetPolicy(policyPath string) (PolicyContent, error) {
   226  	var policyContentStruct PolicyContent
   227  	policyContent, err := ioutil.ReadFile(policyPath)
   228  	if err != nil {
   229  		return policyContentStruct, errors.Wrap(err, "unable to read policy file")
   230  	}
   231  	if err := json.Unmarshal(policyContent, &policyContentStruct); err != nil {
   232  		return policyContentStruct, errors.Wrapf(err, "could not parse trust policies from %s", policyPath)
   233  	}
   234  	return policyContentStruct, nil
   235  }