Skip to content

Commit

Permalink
Merge branch 'main' into using-tea
Browse files Browse the repository at this point in the history
  • Loading branch information
MightyMoud committed Oct 7, 2024
2 parents 4680659 + d70beb3 commit 770469f
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 135 deletions.
1 change: 1 addition & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ changelog:
exclude:
- "^docs:"
- "^test:"
- "^chore:"
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,12 @@ I'm tired of the complexity involved in hosting my side projects. While some pla

## Installation

On a Mac:
Using brew:

```bash
brew install sidekick
```

Linux/Windows you need GO installed on your system then you need to run:

```bash
go install github.com/mightymoud/sidekick@latest
```

## Usage

Sidekick helps you along all the steps of deployment on your VPS. From basic setup to zero downtime deploys, we got you! ✊
Expand Down Expand Up @@ -77,8 +71,10 @@ Then you need to enter the following:

After that Sidekick will setup many things on your VPS - Usually takes around 2 mins

You can use flags instead. Read more [in the docs](https://www.sidekickdeploy.com/docs/command/init/).

<details>
<summary>What does Sidekick do when I run this command</summary>
<summary>What does Sidekick do when I run this command?</summary>

* Login with `root` user
* Make a new user `sidekick` and grant sudo access
Expand All @@ -94,14 +90,20 @@ After that Sidekick will setup many things on your VPS - Usually takes around 2
</details>

<details>
<summary><strong>If you are on a Mac make sure to:</strong></summary>
* Run `ssh-add --apple-use-keychain ~/.ssh/YOUR_KEY` first before running this command.
<summary>Which SSH key will Sidekick use to login?</summary>

Sidekick will look up the default keys in your default .ssh directory in the following order:

This will be fixed soon in our next release where Sidekick will check the default key before grabbing the keys from the ssh agent.
- id_rsa.pub
- id_ecdsa.pub
- id_ed25519.pub

Sidekick will also get all keys from the `ssh-agent` and try them as well. If you want to use a custom key and not a default one, you would need to add the to your agent first by running `ssh-add KEY_FILE`

</details>

Read more details about flags and other options for this command [on the docs](https://www.sidekickdeploy.com/docs/command/init/)

### Launch a new application

<div align="center" >
Expand Down
11 changes: 7 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import (
"github.com/spf13/cobra"
)

var version = "v0.6.2"

var rootCmd = &cobra.Command{
Use: "sidekick",
Short: "CLI to self-host all your apps on a single VPS without vendor locking",
Long: `With sidekick you can deploy any number of applications to a single VPS, connect multiple domains and much more.`,
Use: "sidekick",
Version: version,
Short: "CLI to self-host all your apps on a single VPS without vendor locking",
Long: `With sidekick you can deploy any number of applications to a single VPS, connect multiple domains and much more.`,
}

func Execute() {
Expand All @@ -35,6 +38,6 @@ func Execute() {
}

func init() {
rootCmd.SetVersionTemplate(`{{println .Version}}`)
rootCmd.AddCommand(preview.PreviewCmd)
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
27 changes: 27 additions & 0 deletions render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ limitations under the License.
package render

import (
"os"

"github.com/pterm/pterm"
"github.com/pterm/pterm/putils"
)
Expand All @@ -28,3 +30,28 @@ func RenderSidekickBig() {
pterm.DefaultCenter.Println(s)

}

func RenderKeyValidation(resultLines []string, keyHash string, hostname string) {
startColor := pterm.NewRGB(0, 255, 255)
endColor := pterm.NewRGB(255, 0, 255)

pterm.DefaultCenter.Print(keyHash)
for i := 0; i < len(resultLines[1:]); i++ {
fadeFactor := float32(i) / float32(20)
currentColor := startColor.Fade(0, 1, fadeFactor, endColor)
pterm.DefaultCenter.Print(currentColor.Sprint(resultLines[1:][i]))
}
prompt := pterm.DefaultInteractiveContinue

pterm.DefaultCenter.Printf(pterm.FgYellow.Sprintf("This is the ASCII art and fingerprint of your VPS's public key at %s", hostname))
pterm.DefaultCenter.Printf(pterm.FgYellow.Sprint("Please confirm you want to continue with the connection"))
pterm.DefaultCenter.Printf(pterm.FgYellow.Sprint("Sidekick will add this host/key pair to known_hosts"))
pterm.Println()

prompt.DefaultText = "Would you like to proceed?"
prompt.Options = []string{"yes", "no"}
if result, _ := prompt.Show(); result != "yes" {
pterm.Error.Println("In order to continue, you need to accept this.")
os.Exit(0)
}
}
151 changes: 151 additions & 0 deletions utils/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package utils

import (
"errors"
"fmt"
"log"
"net"
"os"
"os/exec"
"os/user"
"path"
"strings"
"time"

"github.com/mightymoud/sidekick/render"
"github.com/skeema/knownhosts"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)

func getKeyFilesAuth() ([]ssh.AuthMethod, error) {
user, err := user.Current()
if err != nil {
return nil, err
}
sshDir := path.Join(user.HomeDir, ".ssh")
keyFiles := []string{
"id_rsa",
"id_ecdsa",
"id_ed25519",
}

var authMethods []ssh.AuthMethod

for _, keyFile := range keyFiles {
keyPath := path.Join(sshDir, keyFile)
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
continue
}

privateKey, err := os.ReadFile(keyPath)
if err != nil {
continue
}

signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil {
continue
}

authMethods = append(authMethods, ssh.PublicKeys(signer))
}

return authMethods, nil
}

func inspectServerPublicKey(key ssh.PublicKey, hostname string) {
sshKeyCmd := exec.Command("sh", "-s", "-", string(ssh.MarshalAuthorizedKey(key)))
sshKeyCmd.Stdin = strings.NewReader(sshKeyScript)
result, sshKeyCmdErr := sshKeyCmd.Output()
if sshKeyCmdErr != nil {
panic(sshKeyCmdErr)
}
resultLines := strings.Split(string(result), "\n")
keyHash := resultLines[0]

render.RenderKeyValidation(resultLines, keyHash, hostname)

}

func GetSshClient(server string, sshUser string) (*ssh.Client, error) {
sshPort := "22"
sshAgentSock := os.Getenv("SSH_AUTH_SOCK")
if sshAgentSock == "" {
log.Fatal("No SSH SOCK AVAILABLE")
return nil, errors.New("Error happened connecting to ssh-agent")
}

conn, err := net.Dial("unix", sshAgentSock)
if err != nil {
log.Fatalf("Failed to connect to SSH agent: %s", err)
return nil, err
}
defer conn.Close()

agentClient := agent.NewClient(conn)

// Get auth of standard keys not in agent
authMethods, _ := getKeyFilesAuth()

authMethods = append(authMethods, ssh.PublicKeysCallback(agentClient.Signers))

cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error {
currentUser, _ := user.Current()
khPath := fmt.Sprintf("%s/.ssh/known_hosts", currentUser.HomeDir)
kh, knErr := knownhosts.NewDB(khPath)
if knErr != nil {
log.Fatalf("Failed to read known_hosts: %s", err)
os.Exit(1)
}

innerCallback := kh.HostKeyCallback()
err := innerCallback(hostname, remote, key)
if knownhosts.IsHostKeyChanged(err) {
return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack.", hostname)
} else if knownhosts.IsHostUnknown(err) {
inspectServerPublicKey(key, hostname)
f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0600)
if ferr == nil {
defer f.Close()
ferr = knownhosts.WriteKnownHost(f, hostname, remote, key)
} else {
log.Printf("Failed to add host %s to known_hosts: %v\n", hostname, ferr)
}
return nil
}
return err
})

var client *ssh.Client

// This error will be thrown when one method/key doesn't work
var expectedClientErr = errors.New("ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain")
for _, method := range authMethods {
config := &ssh.ClientConfig{
User: sshUser,
Auth: []ssh.AuthMethod{method},
HostKeyCallback: cb,
Timeout: 5 * time.Second,
}

workingClient, sshClientErr := ssh.Dial("tcp", fmt.Sprintf("%s:%s", server, sshPort), config)
if sshClientErr != nil {
if sshClientErr.Error() != expectedClientErr.Error() {
log.Fatalf("Failed to create ssh client to the server: %v", sshClientErr)
}
continue
}
client = workingClient
break
}
return client, nil
}

func Login(server string, user string) (*ssh.Client, error) {
sshClient, err := GetSshClient(server, user)
if err != nil {
return nil, err
}
return sshClient, nil
}
Loading

0 comments on commit 770469f

Please sign in to comment.