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
ccc09759
Commit
ccc09759
authored
Feb 28, 2019
by
Lukas Burgey
Browse files
Implement sophisticated key negotiation
Closes
#7
parent
342b7433
Changes
1
Hide whitespace changes
Inline
Side-by-side
main.go
View file @
ccc09759
...
...
@@ -25,7 +25,7 @@ import (
var
(
app
=
kingpin
.
New
(
"FEUDAL SSH"
,
"SSH inte
r
gration for FEUDAL"
,
"SSH integration for FEUDAL"
,
)
.
Author
(
"Lukas Burgey"
,
)
.
Version
(
...
...
@@ -41,7 +41,7 @@ var (
issuerURI
=
app
.
Flag
(
"issuer"
,
"Issuer URI of your access token"
)
.
Short
(
'i'
)
.
Default
(
"https://unity.helmholtz-data-federation.de/oauth2"
)
.
String
()
feudalURI
=
app
.
Flag
(
"uri"
,
"Feudal URI"
)
.
Short
(
'u'
)
.
Default
(
"https://hdf-portal-dev.data.kit.edu"
)
.
String
()
pubKey
=
app
.
Flag
(
"pubkey"
,
"SSH public key file path"
)
.
Short
(
'k'
)
.
Required
()
.
String
()
pubKey
=
app
.
Flag
(
"pubkey"
,
"SSH public key file path"
)
.
Short
(
'k'
)
.
String
()
)
func
restCall
(
method
string
,
path
string
,
body
io
.
Reader
)
(
responseBytes
[]
byte
,
err
error
)
{
...
...
@@ -96,65 +96,152 @@ func fetchDepState(id int) (state api.DepState) {
return
}
func
readPubKey
()
(
name
,
key
string
)
{
// determine which file to use
path
:=
*
pubKey
switch
filepath
.
Ext
(
path
)
{
case
".pub"
:
// all nice and good
case
""
:
// add extension (probably a private key file was provided)
path
+=
".pub"
default
:
// unusable extension
log
.
Fatalf
(
"Unusable file: %s"
,
path
)
// select a local key for reading
func
selectLocalKey
()
(
selected
string
)
{
var
sshDir
=
filepath
.
Join
(
os
.
ExpandEnv
(
"$HOME"
),
".ssh"
)
// check if the user gave a valid argument
if
*
pubKey
!=
""
{
if
filepath
.
Ext
(
*
pubKey
)
==
".pub"
{
if
_
,
err
:=
os
.
Stat
(
*
pubKey
);
os
.
IsExist
(
err
)
{
selected
=
*
pubKey
return
}
}
else
{
if
_
,
err
:=
os
.
Stat
(
*
pubKey
+
".pub"
);
os
.
IsExist
(
err
)
{
selected
=
*
pubKey
+
".pub"
return
}
}
}
if
_
,
err
:=
os
.
Stat
(
path
);
err
!=
nil
{
if
os
.
IsNotExist
(
err
)
{
log
.
Fatalf
(
"File does not exist: %s"
,
path
)
// 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
)
}
}
}
log
.
Fatal
(
err
)
}
keyBytes
,
err
:=
ioutil
.
ReadFile
(
path
)
// the ssh dir is empty!?
log
.
Fatalf
(
"Please specify an SSH public key"
)
return
}
func
readPubKey
()
(
name
,
key
string
)
{
selected
:=
selectLocalKey
()
name
=
filepath
.
Base
(
selected
)
keyBytes
,
err
:=
ioutil
.
ReadFile
(
selected
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
name
=
filepath
.
Base
(
path
)
key
=
string
(
keyBytes
)
return
}
// keyExists check if the given key / name combination was already uploaded to feudal
func
keyExists
(
name
,
key
string
)
bool
{
func
fetchUpstreamKeys
()
(
keys
[]
api
.
SSHKey
,
err
error
)
{
// fetch key list
keyListResponse
,
err
:=
restCall
(
"GET"
,
"ssh-keys"
,
nil
)
if
err
!=
nil
{
return
false
log
.
Printf
(
"Fetching upstream keys: %s"
,
err
)
return
}
var
sshKeys
=
[]
api
.
SSHKey
{}
err
=
json
.
Unmarshal
(
keyListResponse
,
&
sshKeys
)
err
=
json
.
Unmarshal
(
keyListResponse
,
&
keys
)
if
err
!=
nil
{
return
false
return
}
for
_
,
k
:=
range
sshKeys
{
// the backend strips comments so we adjust our check
if
k
.
Name
==
name
&&
strings
.
HasPrefix
(
key
,
k
.
Key
)
{
return
true
return
}
// make sure at least one public key for which we have the private key is uploaded at the backend
func
negotiatePublicKey
()
(
selectedKey
api
.
SSHKey
)
{
// keys at feudal
upstreamKeys
,
err
:=
fetchUpstreamKeys
()
if
err
!=
nil
{
}
// ssh dir
homeDir
,
_
:=
os
.
UserHomeDir
()
sshPath
:=
filepath
.
Join
(
homeDir
,
".ssh"
)
log
.
Printf
(
"SSH path: %s"
,
sshPath
)
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
]
string
)
for
_
,
localKey
:=
range
localKeys
{
if
filepath
.
Ext
(
localKey
.
Name
())
==
".pub"
{
name
:=
filepath
.
Base
(
localKey
.
Name
())
keyBytes
,
err
:=
ioutil
.
ReadFile
(
filepath
.
Join
(
sshPath
,
localKey
.
Name
()))
if
err
==
nil
{
localKeyMap
[
name
]
=
string
(
keyBytes
)
}
}
}
return
false
}
func
uploadPubKey
()
(
state
api
.
DepState
)
{
// check if we have any of them
for
_
,
upstreamKey
:=
range
upstreamKeys
{
if
key
,
ok
:=
localKeyMap
[
upstreamKey
.
Name
];
ok
&&
strings
.
HasPrefix
(
key
,
upstreamKey
.
Key
)
{
selectedKey
=
upstreamKey
log
.
Printf
(
"Upstream has key:
\t
%s (found locally)"
,
upstreamKey
.
Name
)
return
}
}
name
,
key
:=
readPubKey
()
if
keyExists
(
name
,
key
)
{
log
.
Printf
(
"Key already uploaded: %s"
,
name
)
return
// check we have any of them with different filenames
for
_
,
upstreamKey
:=
range
upstreamKeys
{
for
lName
,
lKey
:=
range
localKeyMap
{
if
lKey
==
upstreamKey
.
Key
{
selectedKey
=
api
.
SSHKey
{
Name
:
lName
,
Key
:
lKey
,
}
log
.
Printf
(
"Upstream has key:
\t
%s (found locally as %s)"
,
upstreamKey
.
Name
,
lName
)
return
}
}
}
log
.
Printf
(
"We have no common key with upstream"
)
// we need to upload a key
name
,
key
:=
readPubKey
()
log
.
Printf
(
"Uploading key %s"
,
name
)
body
:=
map
[
string
]
string
{
"name"
:
name
,
"key"
:
key
}
...
...
@@ -162,18 +249,17 @@ func uploadPubKey() (state api.DepState) {
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
response
,
err
:
=
restCall
(
"POST"
,
"ssh-keys"
,
bytes
.
NewReader
(
bodyBytes
))
_
,
err
=
restCall
(
"POST"
,
"ssh-keys"
,
bytes
.
NewReader
(
bodyBytes
))
if
err
!=
nil
{
log
.
Fatalf
(
"Uploading key: %s"
,
err
)
}
if
responseIdented
,
err
:=
json
.
MarshalIndent
(
response
,
""
,
" "
);
err
==
nil
{
log
.
Printf
(
"Response: %s"
,
responseIdented
)
}
else
{
log
.
Printf
(
"Response: %s"
,
response
)
return
api
.
SSHKey
{
Name
:
name
,
Key
:
key
,
}
return
}
// determined the id of the requested service
func
findServiceID
()
(
serviceID
int
)
{
serviceBytes
,
err
:=
restCall
(
"GET"
,
"services"
,
nil
)
if
err
!=
nil
{
...
...
@@ -203,7 +289,7 @@ func findServiceID() (serviceID int) {
for
_
,
s
:=
range
services
{
if
s
.
Name
==
*
serviceName
{
serviceID
=
s
.
ID
log
.
Printf
(
"Selected service
'%s' (id: %d)
"
,
*
serviceName
,
serviceID
)
log
.
Printf
(
"Selected service
:
\t
%s
"
,
*
serviceName
)
return
}
}
...
...
@@ -286,9 +372,9 @@ func inspectAccessToken(at string) (issuerURI string) {
}
var
payloadBytesIndented
=
bytes
.
NewBuffer
([]
byte
{})
if
json
.
Indent
(
payloadBytesIndented
,
payloadBytes
,
""
,
" "
)
==
nil
{
log
.
Printf
(
"JWT access token payload:
%s"
,
payloadBytesIndented
.
Bytes
())
log
.
Printf
(
"JWT access token payload:
\n
%s"
,
payloadBytesIndented
.
Bytes
())
}
else
{
log
.
Printf
(
"JWT access token payload:
%s"
,
payloadBytes
)
log
.
Printf
(
"JWT access token payload:
\n
%s"
,
payloadBytes
)
}
payload
:=
map
[
string
]
interface
{}{}
if
err
:=
json
.
Unmarshal
(
payloadBytes
,
&
payload
);
err
!=
nil
{
...
...
@@ -318,9 +404,11 @@ func main() {
}
log
.
Printf
(
"Using issuer: %s"
,
*
issuerURI
)
var
serviceID
=
findServiceID
()
negotiatedKey
:=
negotiatePublicKey
()
*
pubKey
=
negotiatedKey
.
Name
log
.
Printf
(
"Selected ssh key:
\t
%s"
,
*
pubKey
)
uploadPubKey
()
var
serviceID
=
findServiceID
()
// start the deployment
var
deployment
api
.
Deployment
...
...
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