diff --git a/README.md b/README.md index 48db4f0..c074fe1 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ During setup, you'll be prompted to choose between using your own server or the ### Uploading Keys +**NOTE**: Uploading keys will overwrite your existing keys on the server. Later versions of ssh-sync may include a conflict resolution for upload, but it is currently not implemented. + To upload your SSH keys and configuration to the server, run: ```shell @@ -72,8 +74,6 @@ This command securely transmits your SSH keys and configuration to the chosen se ### Downloading Keys -**NOTE**: ssh-sync currently downloads files to a directory named `.ssh-sync-data`. This is to avoid altering a user's ssh keys as this data is sensitive. v1 release of ssh-sync will include a toggle to use this mode, or to use .ssh-sync directly, and will also include additional safety features like checking with the user before replacing files that have different content. - To download your SSH keys to a new or existing machine, ensuring it's set up for remote access, use: ```shell @@ -82,6 +82,26 @@ ssh-sync download This command fetches your SSH keys from the server, setting up your SSH environment on the machine. +#### Conflict Resolution + +In case there is a difference between a local file and one on your server, ssh-sync will let you know and you can opt to overwrite, skip, or save a file with a `.duplicate` extension for you to review manually. + +```shell +diff detected for my_key. +1. Overwrite +2. Skip +3. Save new file (as .duplicate extension for manual resolution) +Please choose an option (will skip by default): +``` + +#### Safe Mode + +If you simply want to download your keys to a temporary directory, and not interfere with your primary .ssh directory, you may also do this by using the `--safe-mode` (or `-s`) flag. + +```shell +ssh-sync download --safe-mode +``` + ### Challenge Response If setting up a new machine with an existing account, use: @@ -103,11 +123,13 @@ ssh-sync list-machines If you need to remove a machine from your SSH-Sync account, use: ```shell -ssh-sync remove-machine +ssh-sync remove-machine ``` Specify the machine you wish to remove following the command. +You may optionally provide the machine name on the command line so you don't have to type it in when running the command. + ### Reset To remove the current machine from your account and clear all SSH-Sync data: diff --git a/go.mod b/go.mod index 84c090a..315f203 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/samber/lo v1.37.0 + github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.23.7 ) @@ -17,7 +18,7 @@ require ( github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang/mock v1.6.0 // indirect + github.com/kr/pretty v0.1.0 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.5 // indirect @@ -26,13 +27,10 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect - github.com/stretchr/testify v1.9.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect golang.org/x/sys v0.18.0 // indirect - golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d10634b..aa699c5 100644 --- a/go.sum +++ b/go.sum @@ -13,10 +13,13 @@ github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -46,42 +49,16 @@ github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY= github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4= -golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023 h1:0c3L82FDQ5rt1bjTBlchS8t6RQ6299/+5bWMnRLh+uI= -golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 8348b93..714247b 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,14 @@ func main() { Action: actions.Upload, }, { - Name: "download", + Name: "download", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "safe-mode", + Aliases: []string{"s"}, + Usage: "Safe mode will sync to an alternate directory (.ssh-sync-data)", + }, + }, Action: actions.Download, }, { diff --git a/pkg/actions/download.go b/pkg/actions/download.go index 9bfef4f..b992bf0 100644 --- a/pkg/actions/download.go +++ b/pkg/actions/download.go @@ -61,18 +61,25 @@ func Download(c *cli.Context) error { } data.Keys[i].Data = decryptedKey } + isSafeMode := c.Bool("safe-mode") + var directory string + if isSafeMode { + fmt.Println("Executing in safe mode (keys writing to .ssh-sync-data)") + directory = ".ssh-sync-data" + } else { + directory = ".ssh" + } if err := utils.WriteConfig(lo.Map(data.SshConfig, func(config dto.SshConfigDto, i int) models.Host { return models.Host{ Host: config.Host, Values: config.Values, IdentityFiles: config.IdentityFiles, } - })); err != nil { + }), directory); err != nil { return err } for _, key := range data.Keys { - - if err := utils.WriteKey(key.Data, key.Filename); err != nil { + if err := utils.WriteKey(key.Data, key.Filename, directory); err != nil { return err } } diff --git a/pkg/utils/write.go b/pkg/utils/write.go index 7480856..e948791 100644 --- a/pkg/utils/write.go +++ b/pkg/utils/write.go @@ -1,6 +1,7 @@ package utils import ( + "bufio" "errors" "fmt" "os" @@ -10,12 +11,12 @@ import ( "github.com/therealpaulgg/ssh-sync/pkg/models" ) -func WriteConfig(hosts []models.Host) error { +func WriteConfig(hosts []models.Host, sshDirectory string) error { user, err := user.Current() if err != nil { return err } - p := filepath.Join(user.HomeDir, "/.ssh-sync-data") + p := filepath.Join(user.HomeDir, sshDirectory) if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) { if err := os.MkdirAll(p, 0700); err != nil { @@ -49,17 +50,45 @@ func WriteConfig(hosts []models.Host) error { return nil } -func WriteKey(key []byte, filename string) error { +func WriteKey(key []byte, filename string, sshDirectory string) error { user, err := user.Current() if err != nil { return err } - p := filepath.Join(user.HomeDir, "/.ssh-sync-data") + p := filepath.Join(user.HomeDir, sshDirectory) if _, err := os.Stat(p); errors.Is(err, os.ErrNotExist) { if err := os.MkdirAll(p, 0700); err != nil { return err } } + _, err = os.OpenFile(filepath.Join(p, filename), os.O_RDONLY, 0600) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } else if err == nil { + existingData, err := os.ReadFile(filepath.Join(p, filename)) + if err != nil { + return err + } + if string(existingData) != string(key) { + var answer string + scanner := bufio.NewScanner(os.Stdin) + fmt.Printf("diff detected for %s.\n", filename) + fmt.Println("1. Overwrite") + fmt.Println("2. Skip") + fmt.Println("3. Save new file (as .duplicate extension for manual resolution)") + fmt.Print("Please choose an option (will skip by default): ") + + if err := ReadLineFromStdin(scanner, &answer); err != nil { + return err + } + fmt.Println() + if answer == "3" { + filename = filename + ".duplicate" + } else if answer == "2" || answer != "1" { + return nil + } + } + } file, err := os.OpenFile(filepath.Join(p, filename), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { return err @@ -68,5 +97,6 @@ func WriteKey(key []byte, filename string) error { if _, err := file.Write(key); err != nil { return err } + return nil }