Skip to content

Commit

Permalink
Add protected SSH key support (#498)
Browse files Browse the repository at this point in the history
* Remove passphrase flag for ssh keys
* Use interactive cli when adding ssh keys
* Fix permissions errors when adding SSH keys on windows (powershell/cmd.exe)
* chore: go mod tidy

---------

Co-authored-by: Karl Hepworth <[email protected]>
Co-authored-by: Toby Bellwood <[email protected]>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent 8f0e2b5 commit 0229bd3
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 115 deletions.
15 changes: 3 additions & 12 deletions cmd/addkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package cmd

import (
"fmt"

. "github.com/logrusorgru/aurora"

"github.com/pygmystack/pygmy/service/color"
Expand All @@ -39,13 +40,11 @@ var addkeyCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {

Key, _ := cmd.Flags().GetString("key")
Passphrase, _ := cmd.Flags().GetString("passphrase")
var Keys []library.Key

if Key != "" {
thisKey := library.Key{
Path: Key,
Passphrase: Passphrase,
Path: Key,
}
Keys = append(Keys, thisKey)
} else {
Expand All @@ -56,7 +55,7 @@ var addkeyCmd = &cobra.Command{
}

for _, k := range Keys {
if e := library.SshKeyAdd(c, k.Path, k.Passphrase); e != nil {
if e := library.SshKeyAdd(c, k.Path); e != nil {
color.Print(Red(fmt.Sprintf("%v\n", e)))
}
}
Expand All @@ -75,14 +74,6 @@ var addkeyCmd = &cobra.Command{
}

func init() {

rootCmd.AddCommand(addkeyCmd)
addkeyCmd.Flags().StringP("key", "k", "", "Path of SSH key to add")
addkeyCmd.Flags().StringP("passphrase", "p", "", "Passphrase of the SSH key to add")

err := addkeyCmd.Flags().MarkHidden("passphrase")
if err != nil {
fmt.Println(err)
}

}
12 changes: 1 addition & 11 deletions cmd/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ var restartCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {

Key, _ := cmd.Flags().GetString("key")
Passphrase, _ := cmd.Flags().GetString("passphrase")
NoKey, _ := cmd.Flags().GetBool("no-addkey")

if NoKey {
Expand All @@ -54,8 +53,7 @@ var restartCmd = &cobra.Command{

if !FoundKey {
thisKey := library.Key{
Path: Key,
Passphrase: Passphrase,
Path: Key,
}
c.Keys = append(c.Keys, thisKey)
}
Expand All @@ -70,17 +68,9 @@ func init() {

homedir, _ := homedir.Dir()
keypath := fmt.Sprintf("%v%v.ssh%vid_rsa", homedir, string(os.PathSeparator), string(os.PathSeparator))
var passphrase string

rootCmd.AddCommand(restartCmd)
restartCmd.Flags().StringP("key", "", keypath, "Path of SSH key to add")
restartCmd.Flags().StringP("passphrase", "", passphrase, "Passphrase of the SSH key to add")
restartCmd.Flags().BoolP("no-addkey", "", false, "Skip adding the SSH key")
restartCmd.Flags().BoolP("no-resolver", "", false, "Skip adding or removing the Resolver")

err := restartCmd.Flags().MarkHidden("passphrase")
if err != nil {
fmt.Println(err)
}

}
12 changes: 1 addition & 11 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ It includes dnsmasq, haproxy, mailhog, resolv and ssh-agent.`,
Run: func(cmd *cobra.Command, args []string) {

Key, _ := cmd.Flags().GetString("key")
Passphrase, _ := cmd.Flags().GetString("passphrase")
NoKey, _ := cmd.Flags().GetBool("no-addkey")

if NoKey {
Expand All @@ -59,8 +58,7 @@ It includes dnsmasq, haproxy, mailhog, resolv and ssh-agent.`,

if !keyExistsInConfig {
thisKey := library.Key{
Path: Key,
Passphrase: Passphrase,
Path: Key,
}
c.Keys = append(c.Keys, thisKey)
}
Expand All @@ -75,17 +73,9 @@ func init() {

homedir, _ := homedir.Dir()
keypath := fmt.Sprintf("%v%v.ssh%vid_rsa", homedir, string(os.PathSeparator), string(os.PathSeparator))
var passphrase string

rootCmd.AddCommand(upCmd)
upCmd.Flags().StringP("key", "", keypath, "Path of SSH key to add")
upCmd.Flags().StringP("passphrase", "", passphrase, "Passphrase of the SSH key to add")
upCmd.Flags().BoolP("no-addkey", "", false, "Skip adding the SSH key")
upCmd.Flags().BoolP("no-resolver", "", false, "Skip adding or removing the Resolver")

err := upCmd.Flags().MarkHidden("passphrase")
if err != nil {
fmt.Println(err)
}

}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.18.2
golang.org/x/crypto v0.19.0
golang.org/x/term v0.16.0
)

require (
Expand Down
32 changes: 32 additions & 0 deletions service/interface/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,19 @@ func DockerContainerCreate(ID string, config container.Config, hostconfig contai
return resp, err
}

// DockerContainerAttach will return an attached response to a container.
func DockerContainerAttach(ID string, options types.ContainerAttachOptions) (types.HijackedResponse, error) {
cli, ctx, err := NewClient()
if err != nil {
return types.HijackedResponse{}, err
}
resp, err := cli.ContainerAttach(ctx, ID, options)
if err != nil {
return types.HijackedResponse{}, err
}
return resp, err
}

// DockerContainerStart will run an existing container.
func DockerContainerStart(ID string, options types.ContainerStartOptions) error {
cli, ctx, err := NewClient()
Expand All @@ -465,6 +478,25 @@ func DockerContainerStart(ID string, options types.ContainerStartOptions) error
return err
}

// DockerContainerWait will wait for the specificied container condition.
func DockerContainerWait(ID string, condition container.WaitCondition) error {
cli, ctx, err := NewClient()
if err != nil {
return err
}
statusCh, errCh := cli.ContainerWait(ctx, ID, condition)
select {
case err := <-errCh:
if err != nil {
return err
}
case <-statusCh:
return nil
}

return nil
}

// DockerContainerLogs will synchronously (blocking, non-concurrently) print
// logs to stdout and stderr, useful for quick containers with a small amount
// of output which are expected to exit quickly.
Expand Down
108 changes: 96 additions & 12 deletions service/interface/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package model
import (
"context"
"fmt"
"io"
"os"
"strings"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
. "github.com/logrusorgru/aurora"
"golang.org/x/term"

"github.com/pygmystack/pygmy/service/color"
"github.com/pygmystack/pygmy/service/interface/docker"
Expand Down Expand Up @@ -49,6 +53,7 @@ func (Service *Service) Start() error {

name, err := Service.GetFieldString("name")
discrete, _ := Service.GetFieldBool("discrete")
interactive, _ := Service.GetFieldBool("interactive")
output, _ := Service.GetFieldBool("output")
purpose, _ := Service.GetFieldString("purpose")

Expand Down Expand Up @@ -83,20 +88,27 @@ func (Service *Service) Start() error {
}
}

err = Service.DockerRun()
if err != nil {
return err
}
if !interactive {
err = Service.DockerRun()
if err != nil {
return err
}

l, _ := Service.DockerLogs()
if output && string(l) != "" {
fmt.Println(string(l))
}
l, _ := Service.DockerLogs()
if output && string(l) != "" {
fmt.Println(string(l))
}

if c, err := Service.GetRunning(); c.ID != "" {
return nil
} else if err != nil {
return err
if c, err := Service.GetRunning(); c.ID != "" {
return nil
} else if err != nil {
return err
}
} else {
err = Service.DockerRunInteractive()
if err != nil {
return err
}
}

return nil
Expand Down Expand Up @@ -325,6 +337,78 @@ func (Service *Service) DockerRun() error {

}

// DockerRunInteractive will start an interactive container.
func (Service *Service) DockerRunInteractive() error {

ctx := context.Background()
cli, err := client.NewClientWithOpts()
cli.NegotiateAPIVersion(ctx)
if err != nil {
return err
}

name, e := Service.GetFieldString("name")
if e != nil {
return fmt.Errorf("container config is missing label for name")
}

waiter, err := docker.DockerContainerAttach(name, types.ContainerAttachOptions{
Stderr: true,
Stdout: true,
Stdin: true,
Stream: true,
})
if err != nil {
return err
}

// Connect the stdin/stdout/stderr streams to the container.
go func() {
if _, err := io.Copy(os.Stdout, waiter.Reader); err != nil {
panic(fmt.Sprintf("Error streaming Stdout: %s", err))
}
}()

go func() {
if _, err := io.Copy(os.Stderr, waiter.Reader); err != nil {
panic(fmt.Sprintf("Error streaming Stderr: %s", err))
}
}()

go func() {
if _, err := io.Copy(waiter.Conn, os.Stdin); err != nil {
panic(fmt.Sprintf("Error streaming Stdin: %s", err))
}
}()

if err := docker.DockerContainerStart(name, types.ContainerStartOptions{}); err != nil {
return err
}

// Manipulate the terminal raw mode to support passing password prompts.
fd := int(os.Stdin.Fd())
var oldState *term.State
if term.IsTerminal(fd) {
oldState, err = term.MakeRaw(fd)
if err != nil {
return err
}

defer func() {
if err := term.Restore(fd, oldState); err != nil {
panic(fmt.Sprintf("Error restoring terminal: %s", err))
}
}()
}

if err := docker.DockerContainerWait(name, container.WaitConditionNotRunning); err != nil {
return err
}

return nil

}

// DockerCreate will setup and run a given container.
func (Service *Service) DockerCreate() error {

Expand Down
4 changes: 2 additions & 2 deletions service/library/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package library

import (
"fmt"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/volume"
"github.com/imdario/mergo"
Expand Down Expand Up @@ -63,8 +64,7 @@ type StatusJSONStatus struct {

// Key is a struct with SSH key details.
type Key struct {
Path string `yaml:"path"`
Passphrase string `yaml:"passphrase"`
Path string `yaml:"path"`
}

func mergeService(destination model.Service, src *model.Service) (*model.Service, error) {
Expand Down
Loading

0 comments on commit 0229bd3

Please sign in to comment.