github.com/yogeshkumararora/slsa-github-generator@v1.10.1-0.20240520161934-11278bd5afb4/internal/builders/docker/pkg/config.go (about)

     1  // Copyright 2022 SLSA Authors
     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  //     https://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 pkg
    16  
    17  // This file contains functionality and structs for validating and
    18  // representing user inputs and configuration files.
    19  
    20  import (
    21  	"fmt"
    22  	"net/url"
    23  	"strings"
    24  
    25  	toml "github.com/pelletier/go-toml"
    26  	"github.com/yogeshkumararora/slsa-github-generator/internal/utils"
    27  )
    28  
    29  // BuildConfig is a collection of parameters to use for building the artifact.
    30  type BuildConfig struct {
    31  	// The path, relative to the root of the git repository, where the artifact
    32  	// built by the `docker run` command is expected to be found.
    33  	ArtifactPath string `toml:"artifact_path"`
    34  
    35  	// TODO(#1191): Add env and options if needed.
    36  	// Command to pass to `docker run`. The command is taken as an array
    37  	// instead of a single string to avoid unnecessary parsing. See
    38  	// https://docs.docker.com/engine/reference/builder/#cmd and
    39  	// https://man7.org/linux/man-pages/man3/exec.3.html for more details.
    40  	Command []string `toml:"command"`
    41  }
    42  
    43  // Digest specifies a digest values, including the name of the hash function
    44  // that was used for computing the digest.
    45  type Digest struct {
    46  	Alg   string
    47  	Value string
    48  }
    49  
    50  // DockerImage fully specifies a docker image by a URI (e.g., including the
    51  // docker image name and registry), and its digest.
    52  type DockerImage struct {
    53  	Name   string
    54  	Digest Digest
    55  }
    56  
    57  // ToString returns the builder image in the form of NAME@ALG:VALUE.
    58  func (bi *DockerImage) ToString() string {
    59  	return fmt.Sprintf("%s@%s:%s", bi.Name, bi.Digest.Alg, bi.Digest.Value)
    60  }
    61  
    62  // DockerBuildConfig is a convenience class for holding validated user inputs.
    63  type DockerBuildConfig struct {
    64  	SourceRepo      string
    65  	SourceDigest    Digest
    66  	BuilderImage    DockerImage
    67  	BuildConfigPath string
    68  	ForceCheckout   bool
    69  	Verbose         bool
    70  }
    71  
    72  // NewDockerBuildConfig validates the inputs and generates an instance of
    73  // DockerBuildConfig.
    74  func NewDockerBuildConfig(io *InputOptions) (*DockerBuildConfig, error) {
    75  	if err := validateURI(io.SourceRepo); err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	sourceRepoDigest, err := validateDigest(io.GitCommitHash)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	dockerImage, err := validateDockerImage(io.BuilderImage)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	if err = utils.PathIsUnderCurrentDirectory(io.BuildConfigPath); err != nil {
    90  		return nil, fmt.Errorf("invalid build config path: %v", err)
    91  	}
    92  
    93  	return &DockerBuildConfig{
    94  		SourceRepo:      io.SourceRepo,
    95  		SourceDigest:    *sourceRepoDigest,
    96  		BuilderImage:    *dockerImage,
    97  		BuildConfigPath: io.BuildConfigPath,
    98  		ForceCheckout:   io.ForceCheckout,
    99  		Verbose:         io.Verbose,
   100  	}, nil
   101  }
   102  
   103  func validateURI(input string) error {
   104  	_, err := url.Parse(input)
   105  	if err != nil {
   106  		return fmt.Errorf("could not parse string (%q) as URI: %v", input, err)
   107  	}
   108  	return nil
   109  }
   110  
   111  func validateDigest(input string) (*Digest, error) {
   112  	// We expect the input to be of the form ALG:VALUE
   113  	parts := strings.Split(input, ":")
   114  	if len(parts) != 2 {
   115  		return nil, fmt.Errorf("got %s, want ALG:VALUE format", input)
   116  	}
   117  	digest := Digest{
   118  		Alg:   parts[0],
   119  		Value: parts[1],
   120  	}
   121  	return &digest, nil
   122  }
   123  
   124  func validateDockerImage(image string) (*DockerImage, error) {
   125  	imageParts := strings.Split(image, "@")
   126  	if len(imageParts) != 2 {
   127  		return nil, fmt.Errorf("got %s, want NAME@DIGEST format", image)
   128  	}
   129  
   130  	if err := validateURI(imageParts[0]); err != nil {
   131  		return nil, fmt.Errorf("docker image name (%q) is not a valid URI: %v", imageParts[0], err)
   132  	}
   133  
   134  	digest, err := validateDigest(imageParts[1])
   135  	if err != nil {
   136  		return nil, fmt.Errorf("docker image digest (%q) is malformed: %v", imageParts[1], err)
   137  	}
   138  
   139  	dockerImage := DockerImage{
   140  		Name:   imageParts[0],
   141  		Digest: *digest,
   142  	}
   143  
   144  	return &dockerImage, nil
   145  }
   146  
   147  // ToMap returns this instance as a mapping between the algorithm and value.
   148  func (d *Digest) ToMap() map[string]string {
   149  	return map[string]string{d.Alg: d.Value}
   150  }
   151  
   152  // LoadBuildConfigFromFile loads build configuration from a toml file specified
   153  // by the BuildConfigPath of this DockerBuildConfig. An instance of BuildConfig
   154  // is returned on success.
   155  func (dbc *DockerBuildConfig) LoadBuildConfigFromFile() (*BuildConfig, error) {
   156  	return loadBuildConfigFromFile(dbc.BuildConfigPath)
   157  }
   158  
   159  // loadBuildConfigFromFile does not validate the input path, and is therefore
   160  // not exposed. The corresponding method LoadBuildConfigFromFile must be called
   161  // on an instance of DockerBuildConfig which has a validated BuildConfigPath.
   162  func loadBuildConfigFromFile(path string) (*BuildConfig, error) {
   163  	tomlBytes, err := utils.SafeReadFile(path)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("couldn't load toml file: %v", err)
   166  	}
   167  	tomlTree, err := toml.LoadBytes(tomlBytes)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("couldn't create toml tree: %v", err)
   170  	}
   171  
   172  	config := BuildConfig{}
   173  	if err := tomlTree.Unmarshal(&config); err != nil {
   174  		return nil, fmt.Errorf("couldn't unmarshal toml file: %v", err)
   175  	}
   176  
   177  	return &config, nil
   178  }