Skip to content

Commit 8ff6fa6

Browse files
committed
Added KRL
1 parent ecf3030 commit 8ff6fa6

File tree

6 files changed

+137
-20
lines changed

6 files changed

+137
-20
lines changed

app/admin/hosts.py

+2
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ def update_host(hostname):
231231
if ("keyid" in sshPublicCert):
232232
# Delete a cert
233233
sshPublicCerts = list(filter(lambda key: key['keyid'] != sshPublicCert['keyid'], sshPublicCerts))
234+
if (sshca.add_to_krl(key=sshPublicCert['cert'])):
235+
app.logger.debug("Host Cert revoked: " + str(sshPublicCert['keyid']))
234236
else:
235237
# Create a cert
236238
sshPublicCert['keyid'] = keyid

app/admin/users.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def update_user(username):
226226
con = operations.open_ldap_connection()
227227

228228
if ("sshPublicKeys" in req) or ("sshPublicCerts" in req) or ("accountLocked" in req):
229+
sshca = SSHCA()
229230
user = {}
230231
user = search_users(con,'(&(' + LDAP_ATTR_OBJECTCLASS + '=*)(' + LDAP_ATTR_CN + '=' + username + '))').pop()
231232

@@ -251,16 +252,20 @@ def update_user(username):
251252
if ("keyid" in sshPublicKey):
252253
# Delete key
253254
sshPublicKeys = list(filter(lambda key: key['keyid'] != sshPublicKey['keyid'], sshPublicKeys))
255+
if (sshca.add_to_krl(key=sshPublicKey['key'])):
256+
app.logger.debug("Key revoked: " + str(sshPublicKey['keyid']))
254257
else:
255258
# Create a new key
256259
hostGroups = list(map(lambda hostGroup: hostGroup.lower(), sshPublicKey.get("hostGroups")))
257260
if (set(hostGroups).issubset(set(memberOfs))):
258-
sshPublicKey['keyid'] = key_id
259-
sshPublicKey['keytype'] = keytype
260-
sshPublicKey['dateExpire'] = date_expire
261-
key_id += 1
262-
# sshPublicKeys.append(json.dumps(sshPublicKey).encode())
263-
sshPublicKeys.append(sshPublicKey)
261+
if not (sshca.is_key_revoked(sshPublicKey["key"])):
262+
sshPublicKey['keyid'] = key_id
263+
sshPublicKey['keytype'] = keytype
264+
sshPublicKey['dateExpire'] = date_expire
265+
key_id += 1
266+
sshPublicKeys.append(sshPublicKey)
267+
else:
268+
raise KeyperError(errors["SSHPublicKeyRevokedError"].get("msg"), errors["SSHPublicKeyRevokedError"].get("status"))
264269
else:
265270
raise KeyperError(errors["UnauthorizedAccessError"].get("msg"), errors["UnauthorizedAccessError"].get("status"))
266271

@@ -276,7 +281,6 @@ def update_user(username):
276281
app.logger.debug("key_id: " + str(key_id))
277282

278283
if ("sshPublicCerts" in req):
279-
sshca = SSHCA()
280284
principal_list = user.get("principal")
281285
principal = ','.join(principal_list)
282286

@@ -285,6 +289,8 @@ def update_user(username):
285289
# Delete Cert
286290
sshPublicCerts = list(filter(lambda key: key["keyid"] != sshPublicCert["keyid"], sshPublicCerts))
287291
app.logger.debug("Remaining certs: " + json.dumps(sshPublicCerts))
292+
if (sshca.add_to_krl(key=sshPublicCert['cert'])):
293+
app.logger.debug("User Cert revoked: " + str(sshPublicCert['keyid']))
288294
else:
289295
hostGroups = list(map(lambda hostGroup: hostGroup.lower(), sshPublicCert.get("hostGroups")))
290296
if (set(hostGroups).issubset(set(memberOfs))):

app/public/authkeys.py

+37-5
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
import sys
1515
import json
1616
import ldap
17-
from flask import request, Response
17+
from flask import request, Response, send_from_directory
1818
from flask import current_app as app
1919
from marshmallow import fields, Schema
2020
from marshmallow.validate import Length
2121
from datetime import datetime
2222
from . import public
2323
from ..resources.errors import KeyperError, errors
2424
from ..utils import operations
25+
from ..utils.sshca import SSHCA
2526
from ..admin.users import search_users, cn_from_dn
2627
from ..admin.hosts import searchHosts
2728
from ldapDefn import *
@@ -45,12 +46,22 @@ def get_authkeys():
4546
username = request.values.get('username')
4647
host = request.values.get('host')
4748
fingerprint = request.values.get('fingerprint')
49+
key = request.values.get('key')
4850

4951
app.logger.debug("username/host: " + username + "/" + host)
5052

53+
sshca = SSHCA()
54+
5155
sshPublicKeys = []
5256
result = ""
5357

58+
if (key is None):
59+
app.logger.debug("Key is None")
60+
else:
61+
if (sshca.is_key_revoked(key.replace('#',' ',1))):
62+
app.logger.info("Key in KRL")
63+
return Response(result, mimetype='text/plain')
64+
5465
con = operations.open_ldap_connection()
5566

5667
users = []
@@ -66,8 +77,6 @@ def get_authkeys():
6677
else:
6778
app.logger.debug("User not allowed to access host: " + user[LDAP_ATTR_CN] + "/" + host)
6879

69-
# for sshPublicKey in sshPublicKeys:
70-
# result = sshPublicKey + "\n"
7180
result = '\n'.join(sshPublicKeys)
7281

7382
operations.close_ldap_connection(con)
@@ -92,12 +101,22 @@ def get_authprinc():
92101
username = request.values.get('username')
93102
host = request.values.get('host')
94103
fingerprint = request.values.get('fingerprint')
104+
cert = request.values.get('cert')
95105

96106
app.logger.debug("username/host/fingerprint: " + username + "/" + host + "/" + fingerprint)
97107

98108
sshPublicCerts = []
99109
result = ""
100110

111+
sshca = SSHCA()
112+
113+
if (cert is None):
114+
app.logger.debug("Cert is None")
115+
else:
116+
if (sshca.is_key_revoked(cert.replace('#',' ',1))):
117+
app.logger.info("Cert in KRL")
118+
return Response(result, mimetype='text/plain')
119+
101120
con = operations.open_ldap_connection()
102121

103122
users = []
@@ -232,6 +251,17 @@ def get_userca():
232251
app.logger.debug("Exit")
233252
return Response(ca_key, mimetype='text/plain')
234253

254+
@public.route('/cakrl', methods=['GET', 'POST'])
255+
def get_cakrl():
256+
''' Get KRL File '''
257+
app.logger.debug("Enter")
258+
259+
try:
260+
return send_from_directory(directory=app.config["SSH_CA_DIR"], filename=app.config["SSH_CA_KRL_FILE"], as_attachment=True)
261+
except FileNotFoundError:
262+
app.logger.error("KRL FIle Not Found Exception")
263+
raise KeyperError("KRL File Not Found Exception",404)
264+
235265
@public.route('/usercert', methods=['GET'])
236266
def get_usercert():
237267
''' Get Cert for a user '''
@@ -397,17 +427,19 @@ class AuthKeySchema(Schema):
397427
username = fields.Str(required=True, validate=Length(max=100))
398428
host = fields.Str(required=True, validate=Length(max=100))
399429
fingerprint = fields.Str(required=False, validate=Length(max=100))
430+
key = fields.Str(required=False, validate=Length(max=5000))
400431

401432
class Meta:
402-
fields = ("username", "host", "fingerprint")
433+
fields = ("username", "host", "fingerprint", "key")
403434

404435
class AuthPrincSchema(Schema):
405436
username = fields.Str(required=True, validate=Length(max=100))
406437
host = fields.Str(required=True, validate=Length(max=100))
407438
fingerprint = fields.Str(required=True, validate=Length(max=100))
439+
cert = fields.Str(required=False, validate=Length(max=5000))
408440

409441
class Meta:
410-
fields = ("username", "host", "fingerprint")
442+
fields = ("username", "host", "fingerprint", "cert")
411443

412444
class HostCertSchema(Schema):
413445
hostname = fields.Str(required=True, validate=Length(max=100))

app/resources/errors.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,30 @@ def to_dict(self):
7171
},
7272
"SSHPublicKeyError": {
7373
"msg": "SSH Public Key Error",
74-
"status": 401
74+
"status": 403
7575
},
7676
"SSHPublicKeyParseError": {
7777
"msg": "SSH Public Key Parse Error",
78-
"status": 401
78+
"status": 403
7979
},
8080
"SSHPublicKeyInvalidError": {
8181
"msg": "SSH Public Key Invalid",
82-
"status": 401
82+
"status": 403
83+
},
84+
"SSHPublicKeyRevokedError": {
85+
"msg": "SSH Public Key in Key Revocation List (KRL).",
86+
"status": 403
8387
},
8488
"OSError": {
8589
"msg": "OS Error",
86-
"status": 401
90+
"status": 403
8791
},
8892
"SSHPublicCertError": {
8993
"msg": "SSH Public Cert Error",
90-
"status": 401
94+
"status": 403
9195
},
9296
"SSHPublicCertNotExistError": {
9397
"msg": "SSH Public Cert does not exist",
94-
"status": 401
98+
"status": 403
9599
}
96100
}

app/utils/sshca.py

+74-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class SSHCA(object):
2626
''' SSHCA Class '''
2727
ca_host_key = ''
2828
ca_user_key = ''
29+
ca_krl_file = ''
2930
ca_tmp_work_dir = ''
3031
ca_tmp_work_delete_flag = True
3132

@@ -34,6 +35,7 @@ def __init__(self):
3435
self.ca_dir = app.config["SSH_CA_DIR"]
3536
self.ca_host_key = self.ca_dir + "/" + app.config["SSH_CA_HOST_KEY"]
3637
self.ca_user_key = self.ca_dir + "/" + app.config["SSH_CA_USER_KEY"]
38+
self.ca_krl_file = self.ca_dir + "/" + app.config["SSH_CA_KRL_FILE"]
3739
self.ca_tmp_work_dir = self.ca_dir + "/" + app.config["SSH_CA_TMP_WORK_DIR"]
3840
self.ca_tmp_work_delete_flag = app.config["SSH_CA_TMP_DELETE_FLAG"]
3941
app.logger.debug("Exit")
@@ -42,7 +44,7 @@ def sign_user_key(self, userkey, duration, owner, principal_list):
4244
''' Sign User Key using User CA Key '''
4345
app.logger.debug("Enter")
4446

45-
serial = random.getrandbits(64)
47+
serial = datetime.utcnow().strftime("%Y%m%d%H%M%S") + str(random.getrandbits(16))
4648
signed_key = ''
4749
cert_file_full_path = ''
4850

@@ -89,7 +91,7 @@ def sign_host_key(self, hostkey, duration, hostname, principal_list):
8991
''' Sign Host Key using Host CA Key '''
9092
app.logger.debug("Enter")
9193

92-
serial = random.getrandbits(64)
94+
serial = datetime.utcnow().strftime("%Y%m%d%H%M%S") + str(random.getrandbits(16))
9395
signed_key = ''
9496
cert_file_full_path = ''
9597

@@ -133,3 +135,73 @@ def sign_host_key(self, hostkey, duration, hostname, principal_list):
133135
app.logger.debug("Exit")
134136
return signed_key
135137

138+
def add_to_krl(self, key):
139+
''' Adds Key/Certificate to the Key Revocation List (KRL) '''
140+
app.logger.debug("Enter")
141+
142+
try:
143+
with NamedTemporaryFile(mode='w+t', dir=self.ca_tmp_work_dir, delete=self.ca_tmp_work_delete_flag, suffix='.pub') as key_file:
144+
app.logger.debug("Key: " + key)
145+
key_file.write(key)
146+
key_file.flush()
147+
148+
key_file_full_path = key_file.name
149+
app.logger.debug("Key File: " + key_file_full_path)
150+
151+
subprocess.call([
152+
'ssh-keygen',
153+
'-k',
154+
'-u',
155+
'-f', '{}'.format(self.ca_krl_file),
156+
'-q',
157+
key_file_full_path])
158+
159+
key_file.close()
160+
161+
except subprocess.SubprocessError as e:
162+
app.logger.error("ssh-keygen error: " + str(e))
163+
raise KeyperError(errors["SSHPublicKeyError"].get("msg"), errors["SSHPublicKeyError"].get("status"))
164+
except OSError as e:
165+
app.logger.error("OS error: " + str(e))
166+
raise KeyperError(errors["OSError"].get("msg"), errors["OSError"].get("status"))
167+
168+
app.logger.debug("Exit")
169+
return True
170+
171+
def is_key_revoked(self, key):
172+
''' Checks if key in KRL '''
173+
app.logger.debug("Enter")
174+
175+
rc = True
176+
177+
try:
178+
with NamedTemporaryFile(mode='w+t', dir=self.ca_tmp_work_dir, delete=self.ca_tmp_work_delete_flag, suffix='.pub') as key_file:
179+
app.logger.debug("Key: " + key)
180+
key_file.write(key)
181+
key_file.flush()
182+
183+
key_file_full_path = key_file.name
184+
app.logger.debug("Key File: " + key_file_full_path)
185+
186+
result = subprocess.run([
187+
'ssh-keygen',
188+
'-Q',
189+
'-f', '{}'.format(self.ca_krl_file),
190+
key_file_full_path], capture_output=True, text=True)
191+
192+
if ("REVOKED" in result.stdout):
193+
rc = True
194+
else:
195+
rc = False
196+
197+
key_file.close()
198+
199+
except subprocess.SubprocessError as e:
200+
app.logger.error("ssh-keygen error: " + str(e))
201+
raise KeyperError(errors["SSHPublicKeyError"].get("msg"), errors["SSHPublicKeyError"].get("status"))
202+
except OSError as e:
203+
app.logger.error("OS error: " + str(e))
204+
raise KeyperError(errors["OSError"].get("msg"), errors["OSError"].get("status"))
205+
206+
app.logger.debug("Exit")
207+
return rc

config.py

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Config(object):
4545
SSH_CA_DIR = environ.get("SSH_CA_DIR", "/etc/sshca")
4646
SSH_CA_HOST_KEY = environ.get("SSH_CA_HOST_KEY", "ca_host_key")
4747
SSH_CA_USER_KEY = environ.get("SSH_CA_USER_KEY", "ca_user_key")
48+
SSH_CA_KRL_FILE = environ.get("SSH_CA_KRL_FILE", "ca_krl")
4849
SSH_CA_TMP_WORK_DIR = environ.get("SSH_CA_TMP_WORK_DIR", "tmp")
4950
SSH_CA_TMP_DELETE_FLAG = True
5051

0 commit comments

Comments
 (0)