Skip to content

Commit d4e7f3d

Browse files
authored
Merge pull request #40 from Aishwarya-Lad/CI-14277
CI-14277:add support for base64 encoded env secret docker secrets
2 parents 66ecda5 + 2aa1844 commit d4e7f3d

File tree

3 files changed

+180
-59
lines changed

3 files changed

+180
-59
lines changed

app.go

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,16 @@ func Run() {
275275
Usage: "secret key value pair eg id=MYSECRET",
276276
EnvVar: "PLUGIN_SECRET",
277277
},
278+
cli.StringSliceFlag{
279+
Name: "encoded-secrets-from-env",
280+
Usage: "list of secret env that are base64 encoded",
281+
EnvVar: "PLUGIN_ENCODED_ENV_SECRET",
282+
},
283+
cli.BoolFlag{
284+
Name: "decode-env-secret",
285+
Usage: "decode env values default-false",
286+
EnvVar: "PLUGIN_DECODE_ENV_SECRET",
287+
},
278288
cli.StringSliceFlag{
279289
Name: "secrets-from-env",
280290
Usage: "secret key value pair eg secret_name=secret",
@@ -382,33 +392,35 @@ func run(c *cli.Context) error {
382392
ArtifactFile: c.String("artifact-file"),
383393
CacheMetricsFile: c.String("cache-metrics-file"),
384394
Build: Build{
385-
Remote: c.String("remote.url"),
386-
Name: c.String("commit.sha"),
387-
Dockerfile: c.String("dockerfile"),
388-
Context: c.String("context"),
389-
Tags: c.StringSlice("tags"),
390-
Args: c.StringSlice("args"),
391-
ArgsEnv: c.StringSlice("args-from-env"),
392-
Target: c.String("target"),
393-
Squash: c.Bool("squash"),
394-
Pull: c.BoolT("pull-image"),
395-
CacheFrom: c.Generic("cache-from").(*CustomStringSliceFlag).GetValue(),
396-
CacheTo: c.Generic("cache-to").(*CustomStringSliceFlag).GetValue(),
397-
Compress: c.Bool("compress"),
398-
Repo: c.String("repo"),
399-
Labels: c.StringSlice("custom-labels"),
400-
LabelSchema: c.StringSlice("label-schema"),
401-
AutoLabel: c.BoolT("auto-label"),
402-
Link: c.String("link"),
403-
NoCache: c.Bool("no-cache"),
404-
Secret: c.String("secret"),
405-
SecretEnvs: c.StringSlice("secrets-from-env"),
406-
SecretFiles: c.StringSlice("secrets-from-file"),
407-
AddHost: c.StringSlice("add-host"),
408-
Quiet: c.Bool("quiet"),
409-
Platform: c.String("platform"),
410-
SSHAgentKey: c.String("ssh-agent-key"),
411-
BuildxLoad: c.Bool("buildx-load"),
395+
Remote: c.String("remote.url"),
396+
Name: c.String("commit.sha"),
397+
Dockerfile: c.String("dockerfile"),
398+
Context: c.String("context"),
399+
Tags: c.StringSlice("tags"),
400+
Args: c.StringSlice("args"),
401+
ArgsEnv: c.StringSlice("args-from-env"),
402+
Target: c.String("target"),
403+
Squash: c.Bool("squash"),
404+
Pull: c.BoolT("pull-image"),
405+
CacheFrom: c.Generic("cache-from").(*CustomStringSliceFlag).GetValue(),
406+
CacheTo: c.Generic("cache-to").(*CustomStringSliceFlag).GetValue(),
407+
Compress: c.Bool("compress"),
408+
Repo: c.String("repo"),
409+
Labels: c.StringSlice("custom-labels"),
410+
LabelSchema: c.StringSlice("label-schema"),
411+
AutoLabel: c.BoolT("auto-label"),
412+
Link: c.String("link"),
413+
NoCache: c.Bool("no-cache"),
414+
Secret: c.String("secret"),
415+
SecretEnvs: c.StringSlice("secrets-from-env"),
416+
SecretFiles: c.StringSlice("secrets-from-file"),
417+
AddHost: c.StringSlice("add-host"),
418+
Quiet: c.Bool("quiet"),
419+
Platform: c.String("platform"),
420+
SSHAgentKey: c.String("ssh-agent-key"),
421+
BuildxLoad: c.Bool("buildx-load"),
422+
DecodeEnvSecret: c.Bool("decode-env-secret"),
423+
EncodedSecretEnvs: c.StringSlice("encoded-secrets-from-env"),
412424
},
413425
Daemon: Daemon{
414426
Registry: c.String("docker.registry"),

docker.go

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package docker
22

33
import (
4+
"encoding/base64"
45
"encoding/json"
56
"fmt"
7+
"log"
68
"os"
79
"os/exec"
810
"path/filepath"
@@ -53,34 +55,36 @@ type (
5355

5456
// Build defines Docker build parameters.
5557
Build struct {
56-
Remote string // Git remote URL
57-
Name string // Docker build using default named tag
58-
Dockerfile string // Docker build Dockerfile
59-
Context string // Docker build context
60-
Tags []string // Docker build tags
61-
Args []string // Docker build args
62-
ArgsEnv []string // Docker build args from env
63-
Target string // Docker build target
64-
Squash bool // Docker build squash
65-
Pull bool // Docker build pull
66-
CacheFrom []string // Docker buildx cache-from
67-
CacheTo []string // Docker buildx cache-to
68-
Compress bool // Docker build compress
69-
Repo string // Docker build repository
70-
LabelSchema []string // label-schema Label map
71-
AutoLabel bool // auto-label bool
72-
Labels []string // Label map
73-
Link string // Git repo link
74-
NoCache bool // Docker build no-cache
75-
Secret string // secret keypair
76-
SecretEnvs []string // Docker build secrets with env var as source
77-
SecretFiles []string // Docker build secrets with file as source
78-
AddHost []string // Docker build add-host
79-
Quiet bool // Docker build quiet
80-
Platform string // Docker build platform
81-
SSHAgentKey string // Docker build ssh agent key
82-
SSHKeyPath string // Docker build ssh key path
83-
BuildxLoad bool // Docker buildx --load
58+
Remote string // Git remote URL
59+
Name string // Docker build using default named tag
60+
Dockerfile string // Docker build Dockerfile
61+
Context string // Docker build context
62+
Tags []string // Docker build tags
63+
Args []string // Docker build args
64+
ArgsEnv []string // Docker build args from env
65+
Target string // Docker build target
66+
Squash bool // Docker build squash
67+
Pull bool // Docker build pull
68+
CacheFrom []string // Docker buildx cache-from
69+
CacheTo []string // Docker buildx cache-to
70+
Compress bool // Docker build compress
71+
Repo string // Docker build repository
72+
LabelSchema []string // label-schema Label map
73+
AutoLabel bool // auto-label bool
74+
Labels []string // Label map
75+
Link string // Git repo link
76+
NoCache bool // Docker build no-cache
77+
Secret string // secret keypair
78+
SecretEnvs []string // Docker build secrets with env var as source
79+
SecretFiles []string // Docker build secrets with file as source
80+
AddHost []string // Docker build add-host
81+
Quiet bool // Docker build quiet
82+
Platform string // Docker build platform
83+
SSHAgentKey string // Docker build ssh agent key
84+
SSHKeyPath string // Docker build ssh key path
85+
BuildxLoad bool // Docker buildx --load
86+
DecodeEnvSecret bool // Decode the secret value in env
87+
EncodedSecretEnvs []string // Docker build env secrets that are encoded using base64
8488
}
8589

8690
// Plugin defines the Docker plugin parameters.
@@ -150,7 +154,6 @@ func (p Plugin) Exec() error {
150154
}
151155
time.Sleep(time.Second * 1)
152156
}
153-
154157
// for debugging purposes, log the type of authentication
155158
// credentials that have been provided.
156159
switch {
@@ -165,7 +168,6 @@ func (p Plugin) Exec() error {
165168
default:
166169
fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.")
167170
}
168-
169171
// create Auth Config File
170172
if p.Login.Config != "" {
171173
os.MkdirAll(dockerHome, 0600)
@@ -467,6 +469,30 @@ func commandInfo() *exec.Cmd {
467469
return exec.Command(dockerExe, "info")
468470
}
469471

472+
// helper function to update env var value from base64 encoded to decoded
473+
func updateEnvWithDecodedValue(encodedEnvList []string) error {
474+
for _, envName := range encodedEnvList {
475+
// Get the current base64 encoded value
476+
encodedValue := os.Getenv(envName)
477+
if encodedValue == "" {
478+
return fmt.Errorf("environment variable %s not found", envName)
479+
}
480+
481+
// Decode the base64 value
482+
decodedBytes, err := base64.StdEncoding.DecodeString(encodedValue)
483+
if err != nil {
484+
return fmt.Errorf("failed to decode value for %s: %v", envName, err)
485+
}
486+
487+
// Update the environment variable with the decoded value
488+
err = os.Setenv(envName, string(decodedBytes))
489+
if err != nil {
490+
return fmt.Errorf("failed to set environment variable %s: %v", envName, err)
491+
}
492+
}
493+
return nil
494+
}
495+
470496
// helper function to create the docker buildx command.
471497
func commandBuildx(build Build, builder Builder, dryrun bool, metadataFile string) *exec.Cmd {
472498
args := []string{
@@ -523,6 +549,13 @@ func commandBuildx(build Build, builder Builder, dryrun bool, metadataFile strin
523549
if build.Secret != "" {
524550
args = append(args, "--secret", build.Secret)
525551
}
552+
// update the list of env variables that have been encoded with base64
553+
if build.DecodeEnvSecret {
554+
err := updateEnvWithDecodedValue(build.EncodedSecretEnvs)
555+
if err != nil {
556+
log.Printf("failed to decode harness secrets used as docker secrets in the build command: %v", err)
557+
}
558+
}
526559
for _, secret := range build.SecretEnvs {
527560
if arg, err := getSecretStringCmdArg(secret); err == nil {
528561
args = append(args, "--secret", arg)

docker_test.go

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,90 @@ func TestCommandBuildx(t *testing.T) {
207207
"--metadata-file /tmp/metadata.json",
208208
),
209209
},
210+
{
211+
name: "encoded secrets from env",
212+
build: Build{
213+
Name: "plugins/drone-docker:latest",
214+
Dockerfile: "Dockerfile",
215+
Context: ".",
216+
SecretEnvs: []string{
217+
"foo_secret=FOO_SECRET_ENV_VAR",
218+
},
219+
EncodedSecretEnvs: []string{
220+
"ENCODED_SECRET",
221+
},
222+
DecodeEnvSecret: true,
223+
Repo: "plugins/drone-docker",
224+
Tags: []string{"latest"},
225+
},
226+
want: exec.Command(
227+
dockerExe,
228+
"buildx",
229+
"build",
230+
"--rm=true",
231+
"-f",
232+
"Dockerfile",
233+
"-t",
234+
"plugins/drone-docker:latest",
235+
"--push",
236+
".",
237+
"--secret", "id=foo_secret,env=FOO_SECRET_ENV_VAR",
238+
),
239+
},
240+
{
241+
name: "multiple secrets with encoding",
242+
build: Build{
243+
Name: "plugins/drone-docker:latest",
244+
Dockerfile: "Dockerfile",
245+
Context: ".",
246+
SecretEnvs: []string{
247+
"foo_secret=FOO_SECRET_ENV_VAR",
248+
"bar_secret=BAR_SECRET_ENV_VAR",
249+
},
250+
EncodedSecretEnvs: []string{
251+
"ENCODED_SECRET1",
252+
"ENCODED_SECRET2",
253+
},
254+
DecodeEnvSecret: true,
255+
Repo: "plugins/drone-docker",
256+
Tags: []string{"latest"},
257+
},
258+
want: exec.Command(
259+
dockerExe,
260+
"buildx",
261+
"build",
262+
"--rm=true",
263+
"-f",
264+
"Dockerfile",
265+
"-t",
266+
"plugins/drone-docker:latest",
267+
"--push",
268+
".",
269+
"--secret", "id=foo_secret,env=FOO_SECRET_ENV_VAR",
270+
"--secret", "id=bar_secret,env=BAR_SECRET_ENV_VAR",
271+
),
272+
},
210273
}
211274

212275
for _, tc := range tcs {
213276
tc := tc
214-
215277
t.Run(tc.name, func(t *testing.T) {
216-
cmd := commandBuildx(tc.build, tc.builder, tc.dryrun, tc.metadata)
278+
// Set up test environment variables if needed
279+
if tc.build.DecodeEnvSecret && len(tc.build.EncodedSecretEnvs) > 0 {
280+
// Set sample encoded values
281+
os.Setenv("ENCODED_SECRET", "SGVsbG8gV29ybGQ=") // "Hello World" in base64
282+
os.Setenv("ENCODED_SECRET1", "VGVzdFZhbHVlMQ==") // "TestValue1" in base64
283+
os.Setenv("ENCODED_SECRET2", "VGVzdFZhbHVlMg==") // "TestValue2" in base64
217284

285+
// Clean up after test
286+
defer func() {
287+
os.Unsetenv("ENCODED_SECRET")
288+
os.Unsetenv("ENCODED_SECRET1")
289+
os.Unsetenv("ENCODED_SECRET2")
290+
}()
291+
}
292+
293+
cmd := commandBuildx(tc.build, tc.builder, tc.dryrun, tc.metadata)
218294
if !reflect.DeepEqual(cmd.String(), tc.want.String()) {
219295
t.Errorf("Got cmd %v, want %v", cmd, tc.want)
220296
}

0 commit comments

Comments
 (0)