Commit 1536b681 authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Fix some bugs

Closes #11
Closes #12
parent 164ff174
......@@ -9,7 +9,6 @@ import (
"log"
"net/http"
"os"
"path/filepath"
"strings"
"text/tabwriter"
"time"
......@@ -28,7 +27,7 @@ var (
).Author(
"Lukas Burgey",
).Version(
"v0.1.0",
"v0.1.1",
)
httpClient = &http.Client{}
......@@ -49,6 +48,18 @@ var (
sshSession *ssh.Session
)
func fatalf(format string, args ...interface{}) {
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Printf(format, args...)
os.Exit(1)
}
func fatal(err error) {
fatalf("Error: %s", err)
}
func restCall(method string, path string, body io.Reader) (responseBytes []byte, err error) {
request, err := http.NewRequest(method, fmt.Sprintf("%s/rest/%s", *feudalURI, path), body)
if err != nil {
......@@ -101,174 +112,12 @@ func fetchDepState(id int) (state api.DepState) {
return
}
// select a local key for reading
func selectLocalKey(userArg string) (selected string) {
var sshDir = filepath.Join(os.ExpandEnv("$HOME"), ".ssh")
// check if the user gave a valid argument
if userArg != "" {
if filepath.Ext(userArg) == ".pub" {
if _, err := os.Stat(userArg); os.IsExist(err) {
selected = userArg
return
}
} else {
if _, err := os.Stat(userArg + ".pub"); os.IsExist(err) {
selected = userArg + ".pub"
return
}
}
}
// pick a local key
localKeys, err := ioutil.ReadDir(sshDir)
if err != nil {
log.Fatalf("Reading SSH dir: %s", err)
}
if len(localKeys) > 0 {
var keyMap = map[string]struct{}{}
for _, localKey := range localKeys {
keyMap[localKey.Name()] = struct{}{}
}
// check preferred keys
for _, key := range []string{
"id_ed25519",
"id_rsa",
"id_ecdsa",
"id_dsa",
} {
// check if key the pair exists
if _, ok := keyMap[key]; ok {
if _, ok := keyMap[key+".pub"]; ok {
return filepath.Join(sshDir, key+".pub")
}
}
}
// no preferred keys were found, so we take the first available key
for keyName := range keyMap {
if filepath.Ext(keyName) == ".pub" {
// does the corresponding private key exist?
privateKeyName := strings.TrimSuffix(keyName, ".pub")
if _, ok := keyMap[privateKeyName]; ok {
return filepath.Join(sshDir, keyName)
}
}
}
}
// the ssh dir is empty!?
log.Fatalf("Please specify an SSH public key")
return
}
func readPubKey(userArg string) (name, key string) {
selected := selectLocalKey(userArg)
name = filepath.Base(selected)
keyBytes, err := ioutil.ReadFile(selected)
if err != nil {
log.Fatal(err)
}
key = string(keyBytes)
return
}
func fetchUpstreamKeys() (keys []api.SSHKey, err error) {
// fetch key list
keyListResponse, err := restCall("GET", "ssh-keys", nil)
if err != nil {
log.Printf("Fetching upstream keys: %s", err)
return
}
err = json.Unmarshal(keyListResponse, &keys)
if err != nil {
return
}
return
}
// make sure at least one public key for which we have the private key is uploaded at the backend
func negotiatePublicKey(userArg string) (selectedKey api.SSHKey) {
// keys at feudal
upstreamKeys, err := fetchUpstreamKeys()
if err != nil {
}
// ssh dir
homeDir, _ := os.UserHomeDir()
sshPath := filepath.Join(homeDir, ".ssh")
localKeys, err := ioutil.ReadDir(sshPath)
if err != nil {
log.Fatalf("Reading SSH dir: %s", err)
}
// load keys from the users .ssh dir
var localKeyMap = make(map[string]api.SSHKey)
for _, localKey := range localKeys {
if filepath.Ext(localKey.Name()) == ".pub" {
file := filepath.Join(sshPath, localKey.Name())
keyBytes, err := ioutil.ReadFile(file)
if err == nil {
localKeyMap[localKey.Name()] = api.SSHKey{
Name: filepath.Base(localKey.Name()),
Key: string(keyBytes),
File: file,
}
}
}
}
// check if we have any of them:
// A) by filename
for _, upstreamKey := range upstreamKeys {
if key, ok := localKeyMap[upstreamKey.Name]; ok && strings.HasPrefix(key.Key, upstreamKey.Key) {
selectedKey = upstreamKey
log.Printf("Upstream has key: %s (found in %s)", upstreamKey.Name, sshPath)
return
}
}
// B) by key content
for _, upstreamKey := range upstreamKeys {
for _, lKey := range localKeyMap {
if lKey.Key == upstreamKey.Key {
selectedKey = lKey
log.Printf("Upstream has key: %s (found locally as %s)", upstreamKey.Name, lKey.File)
return
}
}
}
log.Printf("We have no common key with upstream")
// we need to upload a key
name, key := readPubKey(userArg)
log.Printf("Uploading key %s", name)
body := map[string]string{"name": name, "key": key}
bodyBytes, err := json.Marshal(body)
if err != nil {
log.Fatal(err)
}
_, err = restCall("POST", "ssh-keys", bytes.NewReader(bodyBytes))
if err != nil {
log.Fatalf("Uploading key: %s", err)
}
return api.SSHKey{
Name: name,
Key: key,
}
}
// determined the id of the requested service
func findService(sid int) (service api.Service) {
func findService(sid int) (service api.Service, err error) {
serviceBytes, err := restCall("GET", "services", nil)
if err != nil {
log.Fatalf("Retrieving services: %s", err)
err = fmt.Errorf("Retrieving services: %s", err)
return
}
var services = []api.Service{}
json.Unmarshal(serviceBytes, &services)
......@@ -368,13 +217,13 @@ func sshConnect(key api.SSHKey, creds api.Credentials) {
// new connection
connection, err := ssh.Dial("tcp", sshHost+":22", sshConfig)
if err != nil {
log.Fatalf("Failed to dial: %s", err)
fatalf("Failed to dial: %s", err)
}
// new session
sshSession, err := connection.NewSession()
if err != nil {
log.Fatalf("Failed to create session: %s", err)
fatalf("Failed to create session: %s", err)
}
defer sshSession.Close()
......@@ -389,14 +238,14 @@ func sshConnect(key api.SSHKey, creds api.Credentials) {
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := sshSession.RequestPty("vt100", 40, 80, modes); err != nil {
log.Fatal("request for pseudo terminal failed: ", err)
fatalf("request for pseudo terminal failed: %s", err)
}
if err := sshSession.Shell(); err != nil {
log.Fatalf("Shell: %s", err)
fatalf("Shell: %s", err)
}
if err := sshSession.Wait(); err != nil {
log.Fatalf("Wait: %s", err)
fatalf("Wait: %s", err)
}
} else {
fmt.Fprintf(os.Stderr, "FEUDAL> Deployment successful\n")
......@@ -404,10 +253,6 @@ func sshConnect(key api.SSHKey, creds api.Credentials) {
}
}
func pubToPrivKey(pub string) string {
return strings.TrimSuffix(pub, ".pub")
}
// if the access token is a jwt we use the issuer contained in the information
// returns empty string if unable to inpect
func inspectAccessToken(at string) (issuerURI string) {
......@@ -460,24 +305,33 @@ func main() {
log.Printf("Using issuer: %s", issuerURI)
// determine key
var publicKey = negotiatePublicKey(*pubKey)
var publicKey, err = negotiatePublicKey(*pubKey)
if err != nil {
fatalf("Error: Key negotiation failed: %s", err)
}
if publicKey == (api.SSHKey{}) {
fatalf("Error: Key negotiation yielded no key")
}
log.Printf("Selected ssh key: %s", publicKey.File)
// determine service
var service = findService(*serviceID)
service, err := findService(*serviceID)
if err != nil {
fatal(err)
}
// start the deployment
body := strings.NewReader("{\"state_target\":\"deployed\"}")
response, err := restCall("PATCH", fmt.Sprintf("deployment/service/%d", service.ID), body)
if err != nil {
log.Fatalf("Requesting deployment: %s", err)
fatalf("Requesting deployment: %s", err)
}
var deployment api.Deployment
json.Unmarshal(response, &deployment)
if count := len(deployment.States); count != 1 {
log.Printf("Response: %s", response)
log.Fatalf("Got %d states, want 1", count)
fatalf("Got %d states, want 1", count)
}
// wait for the deployment to be executed
......@@ -490,6 +344,6 @@ func main() {
case "failed":
log.Fatal("Deployment failed. Please contact an administrator.")
default:
log.Fatalf("Deployment has state %s: Not implemented", state.State)
fatalf("Deployment has state %s: Not implemented", state.State)
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"os/user"
"path/filepath"
"strings"
api "git.scc.kit.edu/feudal/feudalSSH/api"
)
// selectLocalKey selects a key from the user .ssh dir
//
// This is meant for the case that the user did not provided a key argument
func selectLocalKey() (selected string) {
var sshDir = filepath.Join(os.ExpandEnv("$HOME"), ".ssh")
// pick a local key
localKeys, err := ioutil.ReadDir(sshDir)
if err != nil {
log.Fatalf("Reading SSH dir: %s", err)
}
if len(localKeys) > 0 {
var keyMap = map[string]struct{}{}
for _, localKey := range localKeys {
keyMap[localKey.Name()] = struct{}{}
}
// check preferred keys
for _, key := range []string{
"id_ed25519",
"id_rsa",
"id_ecdsa",
"id_dsa",
} {
// check if key the pair exists
if _, ok := keyMap[key]; ok {
if _, ok := keyMap[key+".pub"]; ok {
return filepath.Join(sshDir, key+".pub")
}
}
}
// no preferred keys were found, so we take the first available key
for keyName := range keyMap {
if filepath.Ext(keyName) == ".pub" {
// does the corresponding private key exist?
privateKeyName := strings.TrimSuffix(keyName, ".pub")
if _, ok := keyMap[privateKeyName]; ok {
return filepath.Join(sshDir, keyName)
}
}
}
}
// the ssh dir is empty!?
log.Fatalf("Please specify an SSH public key")
return
}
func readUserKey(userArg string) (key api.SSHKey, err error) {
if filepath.Ext(userArg) != ".pub" {
userArg += ".pub"
}
var keyBytes []byte
keyBytes, err = ioutil.ReadFile(userArg)
if err != nil {
return
}
key = api.SSHKey{
Name: filepath.Base(userArg),
File: userArg,
// stripping trailing newlines because we can
//Key: strings.TrimSuffix(string(keyBytes), "\n"),
Key: string(keyBytes),
}
return
}
func readSomePubKey() (key api.SSHKey, err error) {
selected := selectLocalKey()
var keyBytes []byte
keyBytes, err = ioutil.ReadFile(selected)
if err != nil {
return
}
key = api.SSHKey{
Name: filepath.Base(selected),
File: selected,
Key: string(keyBytes),
}
return
}
func fetchUpstreamKeys() (keyList []api.SSHKey, err error) {
// fetch key list
keyListResponse, err := restCall("GET", "ssh-keys", nil)
if err != nil {
err = fmt.Errorf("Fetching upstream keys: %s", err)
return
}
err = json.Unmarshal(keyListResponse, &keyList)
if err != nil {
return
}
return
}
// loadLocalKeys loads ssh public keys from the users .ssh dir
func loadLocalKeys() (keyList []api.SSHKey, err error) {
// ssh dir
user, err := user.Current()
if err != nil {
return
}
sshPath := filepath.Join(user.HomeDir, ".ssh")
localKeys, err := ioutil.ReadDir(sshPath)
if err != nil {
return
}
// load keys from the users .ssh dir
for _, localKey := range localKeys {
// dont act on private keys
if filepath.Ext(localKey.Name()) != ".pub" {
continue
}
fullPath := filepath.Join(sshPath, localKey.Name())
keyBytes, err := ioutil.ReadFile(fullPath)
if err == nil {
key := api.SSHKey{
Name: filepath.Base(localKey.Name()),
Key: string(keyBytes),
File: fullPath,
}
keyList = append(keyList, key)
}
}
return
}
// keyExists checks if the given nameArg, keyArg combination exists in the keyMap
func keyExists(keyList []api.SSHKey, nameArg string, keyArg string) (key api.SSHKey, ok bool) {
if nameArg != "" {
for _, k := range keyList {
if k.Name == nameArg {
key = k
ok = true
}
}
// check if the key value is correct
if keyArg != "" && strings.Contains(keyArg, key.Key) {
ok = false
}
return
} // nameArg == ""
// no arguments at all?
if keyArg == "" {
ok = false
return
}
for _, k := range keyList {
if strings.Contains(keyArg, k.Key) {
key = k
ok = true
return
}
}
return
}
func uploadKey(key api.SSHKey) (err error) {
log.Printf("Uploading key %s", key.Name)
if filepath.Ext(key.File) != ".pub" {
err = fmt.Errorf("Not uploading key without '.pub' suffix")
return
}
var bodyBytes []byte
bodyBytes, err = json.Marshal(key)
if err != nil {
return
}
var respBytes []byte
respBytes, err = restCall("POST", "ssh-keys", bytes.NewReader(bodyBytes))
if err != nil {
log.Printf("Request: %s", bodyBytes)
log.Printf("Response: %s", respBytes)
}
return
}
// make sure at least one public key for which we have the private key is uploaded at the backend
func negotiatePublicKey(userArg string) (selectedKey api.SSHKey, err error) {
// Keys at feudal
upstreamKeys, err := fetchUpstreamKeys()
if err != nil {
err = fmt.Errorf("Fetching upstream keys: %s", err)
return
}
// Try using / uploading the users argument
if userArg != "" {
// Read user argument key
var userArgKey api.SSHKey
userArgKey, err = readUserKey(userArg)
if err != nil {
err = fmt.Errorf("Reading key specified in user argument: %s", err)
return
}
selectedKey = userArgKey
// Is this key already uploaded?
_, ok := keyExists(upstreamKeys, "", userArgKey.Key)
if ok {
log.Printf("Upstream has key: %s", userArgKey.File)
return
}
// Key not uploaded -> upload it now
err = uploadKey(userArgKey)
if err == nil {
return
}
}
// No user argument: First be lazy and determine if we actually need to upload a key
// Check if we have any of the keys upstream has:
// Load keys in the .ssh dir
localKeys, err := loadLocalKeys()
if err != nil {
err = fmt.Errorf("Loading local keys: %s", err)
return
}
// A) by filename
for _, upstreamKey := range upstreamKeys {
if key, ok := keyExists(localKeys, upstreamKey.Name, ""); ok {
log.Printf("Upstream has key: %s", key.File)
return key, nil
}
}
// B) by key content
for _, upstreamKey := range upstreamKeys {
if key, ok := keyExists(localKeys, "", upstreamKey.Key); ok {
log.Printf("Upstream has key: %s (found locally as %s)", upstreamKey.Name, key.File)
return key, nil
}
}
log.Printf("We have no common key with upstream")
// upload a key from .ssh
var someKey api.SSHKey
someKey, err = readSomePubKey()
if err != nil {
return
}
selectedKey = someKey
err = uploadKey(someKey)
return
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment