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 }