2019-05-05 12:56:33 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# OpenSSH certificate sign with Hashicorp Vault
|
2019-05-08 14:17:05 +00:00
|
|
|
# - https://github.com/yverry/vault-cert-openssh
|
2019-05-05 12:56:33 +00:00
|
|
|
#
|
|
|
|
# References:
|
|
|
|
# - https://tools.ietf.org/html/rfc4251.html#section-5
|
2019-05-08 14:17:05 +00:00
|
|
|
# - https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
|
2019-05-05 12:56:33 +00:00
|
|
|
# - https://gist.github.com/corny/8264b74a130eb663dbf3d3f0fe0e0ec9
|
2019-05-08 14:17:05 +00:00
|
|
|
|
2019-05-08 13:46:43 +00:00
|
|
|
|
2019-05-05 12:56:33 +00:00
|
|
|
|
|
|
|
import hvac
|
|
|
|
import time, os
|
|
|
|
import base64
|
|
|
|
from struct import unpack
|
|
|
|
|
2019-05-08 14:02:23 +00:00
|
|
|
def vaultRenewKey(filename, vault):
|
2019-05-05 12:56:33 +00:00
|
|
|
sshKey = filename.replace('-cert','')
|
2019-05-08 14:02:23 +00:00
|
|
|
try:
|
|
|
|
public_key = open(sshKey,'r')
|
|
|
|
client = hvac.Client(url=vault['VAULT_ADDR'], token=vault['VAULT_TOKEN'])
|
|
|
|
renew = client.write(vault['VAULT_SSHSIGNPATH'],public_key=public_key.read())
|
2019-05-05 12:56:33 +00:00
|
|
|
|
2019-05-08 14:02:23 +00:00
|
|
|
if len(renew['data']['signed_key']) > 0:
|
|
|
|
s = open(filename,'w')
|
|
|
|
s.write(renew['data']['signed_key'])
|
|
|
|
s.close()
|
|
|
|
except FileNotFoundError:
|
|
|
|
print("OpenSSH Key (%s) is missing" % sshKey)
|
|
|
|
os._exit(-1)
|
2019-05-05 12:56:33 +00:00
|
|
|
|
|
|
|
def Decode(base64encoded):
|
|
|
|
certType, bin = decodeString(base64.b64decode(base64encoded))
|
|
|
|
|
|
|
|
h = {}
|
|
|
|
for typ, key in formats[certType.decode('UTF-8')]:
|
|
|
|
val, bin = typ(bin)
|
|
|
|
h[key] = val
|
|
|
|
return h
|
|
|
|
|
|
|
|
def decodeUint32(value):
|
|
|
|
return unpack('>I', value[:4])[0], value[4:]
|
|
|
|
|
|
|
|
def decodeUint64(value):
|
|
|
|
return unpack('>Q', value[:8])[0], value[8:]
|
|
|
|
|
|
|
|
def decodeMpint(value):
|
|
|
|
size = unpack('>I', value[:4])[0]+4
|
|
|
|
return None, value[size:]
|
|
|
|
|
|
|
|
def decodeString(value):
|
|
|
|
size = unpack('>I', value[:4])[0]+4
|
|
|
|
return value[4:size], value[size:]
|
|
|
|
|
|
|
|
def decodeList(value):
|
|
|
|
joined, remaining = decodeString(value)
|
|
|
|
list = []
|
|
|
|
while len(joined) > 0:
|
|
|
|
elem, joined = decodeString(joined)
|
|
|
|
list.append(elem)
|
|
|
|
return list, remaining
|
|
|
|
|
|
|
|
rsaFormat = [
|
|
|
|
(decodeString, "nonce"),
|
|
|
|
(decodeMpint, "e"),
|
|
|
|
(decodeMpint, "n"),
|
|
|
|
(decodeUint64, "serial"),
|
|
|
|
(decodeUint32, "type"),
|
|
|
|
(decodeString, "key id"),
|
|
|
|
(decodeString, "valid principals"),
|
|
|
|
(decodeUint64, "valid after"),
|
|
|
|
(decodeUint64, "valid before"),
|
|
|
|
(decodeString, "critical options"),
|
|
|
|
(decodeString, "extensions"),
|
|
|
|
(decodeString, "reserved"),
|
|
|
|
(decodeString, "signature key"),
|
|
|
|
(decodeString, "signature"),
|
|
|
|
]
|
|
|
|
|
|
|
|
ecdsaFormat = [
|
|
|
|
(decodeString, "nonce"),
|
|
|
|
(decodeString, "curve"),
|
|
|
|
(decodeString, "public_key"),
|
|
|
|
(decodeUint64, "serial"),
|
|
|
|
(decodeUint32, "type"),
|
|
|
|
(decodeString, "key id"),
|
|
|
|
(decodeString, "valid principals"),
|
|
|
|
(decodeUint64, "valid after"),
|
|
|
|
(decodeUint64, "valid before"),
|
|
|
|
(decodeString, "critical options"),
|
|
|
|
(decodeString, "extensions"),
|
|
|
|
(decodeString, "reserved"),
|
|
|
|
(decodeString, "signature key"),
|
|
|
|
(decodeString, "signature"),
|
|
|
|
]
|
|
|
|
|
|
|
|
ed25519Format = [
|
|
|
|
(decodeString, "nonce"),
|
|
|
|
(decodeString, "pk"),
|
|
|
|
(decodeUint64, "serial"),
|
|
|
|
(decodeUint32, "type"),
|
|
|
|
(decodeString, "key id"),
|
|
|
|
(decodeList, "valid principals"),
|
|
|
|
(decodeUint64, "valid after"),
|
|
|
|
(decodeUint64, "valid before"),
|
|
|
|
(decodeString, "critical options"),
|
|
|
|
(decodeString, "extensions"),
|
|
|
|
(decodeString, "reserved"),
|
|
|
|
(decodeString, "signature key"),
|
|
|
|
(decodeString, "signature"),
|
|
|
|
]
|
|
|
|
|
|
|
|
formats = {
|
|
|
|
"ssh-rsa-cert-v01@openssh.com": rsaFormat,
|
|
|
|
"ecdsa-sha2-nistp256-v01@openssh.com": ecdsaFormat,
|
|
|
|
"ecdsa-sha2-nistp384-v01@openssh.com": ecdsaFormat,
|
|
|
|
"ecdsa-sha2-nistp521-v01@openssh.com": ecdsaFormat,
|
|
|
|
"ssh-ed25519-cert-v01@openssh.com": ed25519Format,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
2019-05-08 14:02:23 +00:00
|
|
|
vault = dict()
|
2019-05-05 12:56:33 +00:00
|
|
|
|
2019-05-08 13:46:43 +00:00
|
|
|
try:
|
2019-05-08 14:02:23 +00:00
|
|
|
vault['VAULT_SSHSIGNPATH'] = os.environ['VAULT_SSHSIGNPATH']
|
|
|
|
vault['VAULT_ADDR'] = os.environ['VAULT_ADDR']
|
2019-05-08 13:46:43 +00:00
|
|
|
except KeyError as e:
|
2019-05-08 14:02:23 +00:00
|
|
|
print('Error %s variable is missing' % str(e))
|
2019-05-08 13:46:43 +00:00
|
|
|
|
2019-05-05 12:56:33 +00:00
|
|
|
try:
|
2019-05-08 14:02:23 +00:00
|
|
|
vault['VAULT_TOKEN'] = os.environ['VAULT_TOKEN']
|
2019-05-05 12:56:33 +00:00
|
|
|
except KeyError:
|
|
|
|
from os.path import expanduser
|
|
|
|
home = expanduser("~")
|
2019-05-08 14:03:32 +00:00
|
|
|
try:
|
|
|
|
o = open(home + '/.vault-token','r')
|
|
|
|
vault['VAULT_TOKEN'] = o.read().splitlines()[0]
|
|
|
|
except FileNotFoundError as e:
|
|
|
|
print('Error %s variable is missing' % str(e))
|
2019-05-05 12:56:33 +00:00
|
|
|
|
|
|
|
if len(sys.argv) > 1:
|
2019-05-05 20:59:35 +00:00
|
|
|
try:
|
|
|
|
with open(sys.argv[1],'r') as f:
|
2019-05-08 14:17:05 +00:00
|
|
|
try:
|
|
|
|
key = Decode(f.read().split(" ")[1])
|
|
|
|
except KeyError as e:
|
|
|
|
print('Unknown key type %s' % str(e))
|
|
|
|
os._exit(-1)
|
|
|
|
|
2019-05-05 20:59:35 +00:00
|
|
|
if int(time.time()) > key['valid before']:
|
2019-05-08 14:02:23 +00:00
|
|
|
print("Need to renew %s" % sys.argv[1])
|
2020-03-22 17:16:09 +00:00
|
|
|
try:
|
|
|
|
vaultRenewKey(sys.argv[1],vault)
|
|
|
|
except hvac.exceptions.VaultDown:
|
|
|
|
print("Vault is sealed, unable to renew SSH Key")
|
2019-05-05 20:59:35 +00:00
|
|
|
except FileNotFoundError:
|
2020-03-22 17:16:09 +00:00
|
|
|
try:
|
|
|
|
vaultRenewKey(sys.argv[1],vault)
|
|
|
|
except hvac.exceptions.VaultDown:
|
|
|
|
print("Vault is sealed, unable to renew SSH Key")
|
2019-05-05 12:56:33 +00:00
|
|
|
else:
|
|
|
|
print("Usage: %s [path to certificate]" % sys.argv[0])
|
2020-03-22 17:16:09 +00:00
|
|
|
exit(1)
|