Skip to content
6 changes: 6 additions & 0 deletions cmd/drone-docker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ func main() {
Usage: "secret key value pairs eg secret_name=/path/to/secret",
EnvVar: "PLUGIN_SECRETS_FROM_FILE",
},
cli.StringFlag{
Name: "ssh-agent",
Usage: "mount ssh agent",
EnvVar: "PLUGIN_SSH_AGENT",
},
cli.StringFlag{
Name: "drone-card-path",
Usage: "card path location to write to",
Expand Down Expand Up @@ -310,6 +315,7 @@ func run(c *cli.Context) error {
Secret: c.String("secret"),
SecretEnvs: c.StringSlice("secrets-from-env"),
SecretFiles: c.StringSlice("secrets-from-file"),
SSHAgent: c.String("ssh-agent"),
AddHost: c.StringSlice("add-host"),
Quiet: c.Bool("quiet"),
},
Expand Down
60 changes: 58 additions & 2 deletions docker.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package docker

import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
Expand All @@ -11,6 +13,11 @@ import (
"time"
)

const (
SSHAgentSockPath = "/tmp/drone-ssh-agent-sock"
SSHPrivateKeyFromEnv = "SSH_KEY"
)

type (
// Daemon defines Docker daemon parameters.
Daemon struct {
Expand Down Expand Up @@ -63,6 +70,7 @@ type (
SecretFiles []string // Docker build secrets with file as source
AddHost []string // Docker build add-host
Quiet bool // Docker build quiet
SSHAgent string // Docker build ssh
}

// Plugin defines the Docker plugin parameters.
Expand Down Expand Up @@ -105,6 +113,7 @@ type (

// Exec executes the plugin step
func (p Plugin) Exec() error {

// start the Docker daemon server
if !p.Daemon.Disabled {
p.startDaemon()
Expand Down Expand Up @@ -178,6 +187,16 @@ func (p Plugin) Exec() error {
cmds = append(cmds, commandPull(img))
}

// setup for using ssh agent (https://docs.docker.com/develop/develop-images/build_enhancements/#using-ssh-to-access-private-data-in-builds)
if p.Build.SSHAgent != "" {
// TODO check in with one of the drone devs...this should not be necessary. I'm probably doing something
// wrong with the cli framework
p.Build.SSHAgent = strings.TrimSuffix(p.Build.SSHAgent, "]")
Copy link
Author

@bkk-bcd bkk-bcd Jul 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what I'm missing here @tphoney -- I'm getting my values for this flag wrapped in []

p.Build.SSHAgent = strings.TrimPrefix(p.Build.SSHAgent, "[")
fmt.Printf("ssh agent set to \"%s\"\n", p.Build.SSHAgent)
cmds = append(cmds, commandSSHAgentForwardingSetup(p.Build)...)
}

cmds = append(cmds, commandBuild(p.Build)) // docker build

for _, tag := range p.Build.Tags {
Expand Down Expand Up @@ -324,6 +343,9 @@ func commandBuild(build Build) *exec.Cmd {
if build.Quiet {
args = append(args, "--quiet")
}
if build.SSHAgent != "" {
args = append(args, "--ssh", build.SSHAgent)
}

if build.AutoLabel {
labelSchema := []string{
Expand All @@ -349,8 +371,8 @@ func commandBuild(build Build) *exec.Cmd {
}
}

// we need to enable buildkit, for secret support
if build.Secret != "" || len(build.SecretEnvs) > 0 || len(build.SecretFiles) > 0 {
// we need to enable buildkit, for secret support and ssh agent support
if build.Secret != "" || len(build.SecretEnvs) > 0 || len(build.SecretFiles) > 0 || build.SSHAgent != "" {
os.Setenv("DOCKER_BUILDKIT", "1")
}
return exec.Command(dockerExe, args...)
Expand Down Expand Up @@ -503,6 +525,40 @@ func commandRmi(tag string) *exec.Cmd {
return exec.Command(dockerExe, "rmi", tag)
}

func commandSSHAgentForwardingSetup(build Build) []*exec.Cmd {
cmds := make([]*exec.Cmd, 0)
if err := writeSSHPrivateKey(); err != nil {
log.Fatalf("unable to setup ssh agent forwarding: %s", err)
}
os.Setenv("SSH_AUTH_SOCK", SSHAgentSockPath)
cmds = append(cmds, exec.Command("ssh-agent", "-a", SSHAgentSockPath))
cmds = append(cmds, exec.Command("ssh-add"))
return cmds
}

func writeSSHPrivateKey() error {
privateKeyBase64 := os.Getenv(SSHPrivateKeyFromEnv)
if privateKeyBase64 == "" {
return fmt.Errorf("%s must be defined and contain the base64 encoded private key to use for ssh agent forwarding", SSHPrivateKeyFromEnv)
}
var err error
privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64)
if err != nil {
return fmt.Errorf("unable to base64 decode private key")
}
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("unable to determine home directory: %s", err)
}
if err := os.MkdirAll(filepath.Join(home, ".ssh"), 0700); err != nil {
return fmt.Errorf("unable to create .ssh directory: %s", err)
}
if err := os.WriteFile(filepath.Join(home, ".ssh", "id_rsa"), privateKey, 0400); err != nil {
return fmt.Errorf("unable to write ssh key: %s", err)
}
return nil
}

// trace writes each command to stdout with the command wrapped in an xml
// tag so that it can be extracted and displayed in the logs.
func trace(cmd *exec.Cmd) {
Expand Down
20 changes: 20 additions & 0 deletions docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,26 @@ func TestCommandBuild(t *testing.T) {
".",
),
},
{
name: "ssh agent",
build: Build{
Name: "plugins/drone-docker:latest",
Dockerfile: "Dockerfile",
Context: ".",
SSHAgent: "default",
},
want: exec.Command(
dockerExe,
"build",
"--rm=true",
"-f",
"Dockerfile",
"-t",
"plugins/drone-docker:latest",
".",
"--ssh default",
),
},
}

for _, tc := range tcs {
Expand Down