Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
feudal
feudalSSH
Commits
1536b681
Commit
1536b681
authored
Mar 15, 2019
by
Lukas Burgey
Browse files
Fix some bugs
Closes
#11
Closes
#12
parent
164ff174
Pipeline
#43633
passed with stages
in 52 seconds
Changes
2
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
main.go
View file @
1536b681
...
...
@@ -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
.
F
atalf
(
"Failed to dial: %s"
,
err
)
f
atalf
(
"Failed to dial: %s"
,
err
)
}
// new session
sshSession
,
err
:=
connection
.
NewSession
()
if
err
!=
nil
{
log
.
F
atalf
(
"Failed to create session: %s"
,
err
)
f
atalf
(
"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
.
F
atal
(
"request for pseudo terminal failed: "
,
err
)
f
atal
f
(
"request for pseudo terminal failed:
%s
"
,
err
)
}
if
err
:=
sshSession
.
Shell
();
err
!=
nil
{
log
.
F
atalf
(
"Shell: %s"
,
err
)
f
atalf
(
"Shell: %s"
,
err
)
}
if
err
:=
sshSession
.
Wait
();
err
!=
nil
{
log
.
F
atalf
(
"Wait: %s"
,
err
)
f
atalf
(
"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
.
F
atalf
(
"Requesting deployment: %s"
,
err
)
f
atalf
(
"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
.
F
atalf
(
"Got %d states, want 1"
,
count
)
f
atalf
(
"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
.
F
atalf
(
"Deployment has state %s: Not implemented"
,
state
.
State
)
f
atalf
(
"Deployment has state %s: Not implemented"
,
state
.
State
)
}
}
sshkeys.go
0 → 100644
View file @
1536b681
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
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment