@@ -19,8 +19,10 @@ package commands
19
19
import (
20
20
"fmt"
21
21
"io"
22
+ "io/fs"
22
23
"os"
23
24
"os/exec"
25
+ "path/filepath"
24
26
"strings"
25
27
"syscall"
26
28
@@ -42,11 +44,14 @@ type RunOutput struct {
42
44
43
45
type RunCommand struct {
44
46
BaseCommand
45
- cmd * instructions.RunCommand
46
- output * RunOutput
47
- shdCache bool
47
+ cmd * instructions.RunCommand
48
+ output * RunOutput
49
+ buildSecrets []string
50
+ shdCache bool
48
51
}
49
52
53
+ const secretsDir = "/run/secrets"
54
+
50
55
// for testing
51
56
var (
52
57
userLookup = util .LookupUser
@@ -57,10 +62,10 @@ func (r *RunCommand) IsArgsEnvsRequiredInCache() bool {
57
62
}
58
63
59
64
func (r * RunCommand ) ExecuteCommand (config * v1.Config , buildArgs * dockerfile.BuildArgs ) error {
60
- return runCommandInExec (config , buildArgs , r .cmd , r .output )
65
+ return runCommandInExec (config , buildArgs , r .cmd , r .output , r . buildSecrets )
61
66
}
62
67
63
- func runCommandInExec (config * v1.Config , buildArgs * dockerfile.BuildArgs , cmdRun * instructions.RunCommand , output * RunOutput ) error {
68
+ func runCommandInExec (config * v1.Config , buildArgs * dockerfile.BuildArgs , cmdRun * instructions.RunCommand , output * RunOutput , buildSecrets [] string ) ( err error ) {
64
69
if output == nil {
65
70
output = & RunOutput {}
66
71
}
@@ -131,6 +136,82 @@ func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun
131
136
return errors .Wrap (err , "adding default HOME variable" )
132
137
}
133
138
139
+ cmdRun .Expand (func (word string ) (string , error ) {
140
+ // NOTE(SasSwart): This is a noop function. It's here to satisfy the buildkit parser.
141
+ // Without this, the buildkit parser won't parse --mount flags for RUN directives.
142
+ // Support for expansion in RUN directives deferred until its needed.
143
+ // https://docs.docker.com/build/building/variables/
144
+ return word , nil
145
+ })
146
+
147
+ buildSecretsMap := make (map [string ]string )
148
+ for _ , s := range buildSecrets {
149
+ secretName , secretValue , found := strings .Cut (s , "=" )
150
+ if ! found {
151
+ return fmt .Errorf ("invalid secret %s" , s )
152
+ }
153
+ buildSecretsMap [secretName ] = secretValue
154
+ }
155
+
156
+ secretFileManager := fileCreatorCleaner {}
157
+ defer func () {
158
+ cleanupErr := secretFileManager .Clean ()
159
+ if err == nil {
160
+ err = cleanupErr
161
+ }
162
+ }()
163
+
164
+ mounts := instructions .GetMounts (cmdRun )
165
+ for _ , mount := range mounts {
166
+ switch mount .Type {
167
+ case instructions .MountTypeSecret :
168
+ // Implemented as per:
169
+ // https://docs.docker.com/reference/dockerfile/#run---mounttypesecret
170
+
171
+ envName := mount .CacheID
172
+ secret , secretSet := buildSecretsMap [envName ]
173
+ if ! secretSet && mount .Required {
174
+ return fmt .Errorf ("required secret %s not found" , mount .CacheID )
175
+ }
176
+
177
+ // If a target is specified, we write to the file specified by the target:
178
+ // If no target is specified and no env is specified, we write to /run/secrets/<id>
179
+ // If no target is specified and an env is specified, we set the env and don't write to file
180
+ if mount .Env == nil || mount .Target != "" {
181
+ targetFile := mount .Target
182
+ if targetFile == "" {
183
+ targetFile = filepath .Join (secretsDir , mount .CacheID )
184
+ }
185
+ if ! filepath .IsAbs (targetFile ) {
186
+ targetFile = filepath .Join (config .WorkingDir , targetFile )
187
+ }
188
+ secretFileManager .MkdirAndWriteFile (targetFile , []byte (secret ), 0700 , 0600 )
189
+ }
190
+
191
+ // We don't return in the block above, because its possible to have both a target and an env.
192
+ // As such we need this guard clause or we risk getting a nil pointer below.
193
+ if mount .Env == nil {
194
+ continue
195
+ }
196
+
197
+ targetEnv := * mount .Env
198
+ if targetEnv == "" {
199
+ targetEnv = mount .CacheID
200
+ }
201
+
202
+ env = append (env , fmt .Sprintf ("%s=%s" , targetEnv , secret ))
203
+ // NOTE(SasSwart):
204
+ // Buildkit v0.16.0 brought support for `RUN --mount` flags. Kaniko support for the mount
205
+ // types below is deferred until its needed.
206
+ // case instructions.MountTypeBind:
207
+ // case instructions.MountTypeTmpfs:
208
+ // case instructions.MountTypeCache:
209
+ // case instructions.MountTypeSSH
210
+ default :
211
+ logrus .Warnf ("Mount type %s is not supported" , mount .Type )
212
+ }
213
+ }
214
+
134
215
cmd .Env = env
135
216
136
217
logrus .Infof ("Running: %s" , cmd .Args )
@@ -153,6 +234,73 @@ func runCommandInExec(config *v1.Config, buildArgs *dockerfile.BuildArgs, cmdRun
153
234
return nil
154
235
}
155
236
237
+ // fileCreatorCleaner keeps tracks of all files and directories that it created in the order that they were created.
238
+ // Once asked to clean up, it will remove all files and directories in the reverse order that they were created.
239
+ type fileCreatorCleaner struct {
240
+ filesToClean []string
241
+ dirsToClean []string
242
+ }
243
+
244
+ func (s * fileCreatorCleaner ) MkdirAndWriteFile (path string , data []byte , dirPerm , filePerm os.FileMode ) error {
245
+ dirPath := filepath .Dir (path )
246
+ parentDirs := strings .Split (dirPath , string (os .PathSeparator ))
247
+
248
+ // Start at the root directory
249
+ currentPath := string (os .PathSeparator )
250
+
251
+ for _ , nextDirDown := range parentDirs {
252
+ if nextDirDown == "" {
253
+ continue
254
+ }
255
+ // Traverse one level down
256
+ currentPath = filepath .Join (currentPath , nextDirDown )
257
+
258
+ if _ , err := filesystem .FS .Stat (currentPath ); errors .Is (err , os .ErrNotExist ) {
259
+ if err := filesystem .FS .Mkdir (currentPath , dirPerm ); err != nil {
260
+ return err
261
+ }
262
+ s .dirsToClean = append (s .dirsToClean , currentPath )
263
+ }
264
+ }
265
+
266
+ // With all parent directories created, we can now create the actual secret file
267
+ if err := filesystem .FS .WriteFile (path , []byte (data ), 0600 ); err != nil {
268
+ return errors .Wrap (err , "writing secret to file" )
269
+ }
270
+ s .filesToClean = append (s .filesToClean , path )
271
+
272
+ return nil
273
+ }
274
+
275
+ func (s * fileCreatorCleaner ) Clean () error {
276
+ for i := len (s .filesToClean ) - 1 ; i >= 0 ; i -- {
277
+ if err := filesystem .FS .Remove (s .filesToClean [i ]); err != nil {
278
+ if errors .Is (err , os .ErrNotExist ) {
279
+ continue
280
+ }
281
+ return err
282
+ }
283
+ }
284
+
285
+ for i := len (s .dirsToClean ) - 1 ; i >= 0 ; i -- {
286
+ if err := filesystem .FS .Remove (s .dirsToClean [i ]); err != nil {
287
+ pathErr := new (fs.PathError )
288
+ // If a path that we need to clean up is not empty, then that means
289
+ // that a third party has placed something in there since we created it.
290
+ // In that case, we should not remove it, because it no longer belongs exclusively to us.
291
+ if errors .As (err , & pathErr ) && pathErr .Err == syscall .ENOTEMPTY {
292
+ continue
293
+ }
294
+ if errors .Is (err , os .ErrNotExist ) {
295
+ continue
296
+ }
297
+ return err
298
+ }
299
+ }
300
+
301
+ return nil
302
+ }
303
+
156
304
// addDefaultHOME adds the default value for HOME if it isn't already set
157
305
func addDefaultHOME (u string , envs []string ) ([]string , error ) {
158
306
for _ , env := range envs {
0 commit comments