diff --git a/scripts/revoke-user-cert b/scripts/revoke-user-cert old mode 100644 new mode 100755 index 05840f0..027b259 --- a/scripts/revoke-user-cert +++ b/scripts/revoke-user-cert @@ -21,7 +21,7 @@ from Crypto.PublicKey import RSA parser = argparse.ArgumentParser(description='Revoke a Tatu-generated user SSH certificate.') parser.add_argument('--projid', '-P', required=True) -parser.add_argument('--serial', '-K', required=True) +parser.add_argument('--serial', '-S', required=True) parser.add_argument('--tatu-url', default= 'http://127.0.0.1:18322', help='URL of the Tatu API') args = parser.parse_args() @@ -38,12 +38,6 @@ if not args.serial.isdigit(): server = args.tatu_url -body = { - 'serial': args.serial, - 'auth_id': auth_id, - 'key.pub': pubkeytext -} - response = requests.post( server + '/revokeduserkeys/' + auth_id, data=json.dumps({'serial': args.serial}) diff --git a/tatu/api/app.py b/tatu/api/app.py index e2b6da1..0fd1f37 100644 --- a/tatu/api/app.py +++ b/tatu/api/app.py @@ -12,7 +12,7 @@ import falcon from oslo_log import log as logging -from tatu import models +from tatu.api import models from tatu.db.persistence import SQLAlchemySessionManager LOG = logging.getLogger(__name__) diff --git a/tatu/api/models.py b/tatu/api/models.py index 018824c..5a8d6a4 100644 --- a/tatu/api/models.py +++ b/tatu/api/models.py @@ -311,7 +311,7 @@ class RevokedUserKeys(object): self.session, auth_id, serial=req.body.get('serial', None), - key=req.body.get('key_id', None), + key_id=req.body.get('key_id', None), cert=req.body.get('cert', None) ) resp.status = falcon.HTTP_OK diff --git a/tatu/db/models.py b/tatu/db/models.py index cc37012..0842953 100644 --- a/tatu/db/models.py +++ b/tatu/db/models.py @@ -67,16 +67,20 @@ def createAuthority(session, auth_id): class UserCert(Base): __tablename__ = 'user_certs' - user_id = sa.Column(sa.String(36), primary_key=True) - fingerprint = sa.Column(sa.String(60), primary_key=True) + serial = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + user_id = sa.Column(sa.String(36)) + fingerprint = sa.Column(sa.String(60)) auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id')) cert = sa.Column(sa.Text) - serial = sa.Column(sa.Integer, index=True, autoincrement=True) revoked = sa.Column(sa.Boolean, default=False) +sa.Index('idx_user_finger', UserCert.user_id, UserCert.fingerprint, unique=True) + def getUserCert(session, user_id, fingerprint): - return session.query(UserCert).get([user_id, fingerprint]) + return session.query(UserCert).filter( + UserCert.user_id == user_id).filter( + UserCert.fingerprint == fingerprint).one_or_none() def getUserCerts(session): @@ -90,21 +94,22 @@ def createUserCert(session, user_id, auth_id, pub): raise falcon.HTTPNotFound( description='No Authority found with that ID') fingerprint = sshpubkeys.SSHKey(pub).hash_md5() - certRecord = session.query(UserCert).get([user_id, fingerprint]) + certRecord = getUserCert(session, user_id, fingerprint) if certRecord is not None: return certRecord - cert = generateCert(getAuthUserKey(auth), pub, - principals='admin,root') - if cert is None: - raise falcon.HTTPInternalServerError( - "Failed to generate the certificate") user = UserCert( user_id=user_id, fingerprint=fingerprint, auth_id=auth_id, - cert=cert ) session.add(user) + session.flush() + user.cert = generateCert(getAuthUserKey(auth), pub, user=True, + principals='admin,root', serial=user.serial) + if user.cert is None: + raise falcon.HTTPInternalServerError( + "Failed to generate the certificate") + session.commit() return user @@ -124,7 +129,9 @@ def getRevokedKeysBase64(session, auth_id): description='No Authority found with that ID') serials = [k.serial for k in session.query(RevokedKey).filter( RevokedKey.auth_id == auth_id)] - return revokedKeysBase64(getAuthUserKey(auth), serials) + user_key = RSA.importKey(getAuthUserKey(auth)) + user_pub_key = user_key.publickey().exportKey('OpenSSH') + return revokedKeysBase64(user_pub_key, serials) def revokeUserKey(session, auth_id, serial=None, key_id=None, cert=None): @@ -140,7 +147,7 @@ def revokeUserKey(session, auth_id, serial=None, key_id=None, cert=None): raise falcon.HTTPBadRequest( "Incorrect CA ID for serial # {}".format(serial)) ser = serial - elif key is not None: + elif key_id is not None: # TODO(pino): look up the UserCert by key id and get the serial number pass elif cert is not None: @@ -242,8 +249,7 @@ def createHostCert(session, token_id, host_id, pub): certRecord = session.query(HostCert).get([host_id, fingerprint]) if certRecord is not None: raise falcon.HTTPConflict('This public key is already signed.') - cert = generateCert(getAuthHostKey(auth), pub, - hostname=token.hostname) + cert = generateCert(getAuthHostKey(auth), pub, user=False) if cert == '': raise falcon.HTTPInternalServerError( "Failed to generate the certificate") diff --git a/tatu/utils.py b/tatu/utils.py index ede8a11..765418d 100644 --- a/tatu/utils.py +++ b/tatu/utils.py @@ -22,7 +22,7 @@ def random_uuid(): return str(uuid.uuid4()) -def generateCert(auth_key, entity_key, hostname=None, principals='root'): +def generateCert(auth_key, entity_key, user=True, principals='root', serial=0): # Temporarily write the authority private key, entity public key to files temp_dir = mkdtemp() ca_file = '/'.join([temp_dir, 'ca_key']) @@ -37,8 +37,8 @@ def generateCert(auth_key, entity_key, hostname=None, principals='root'): with open(pub_file, "w", 0o644) as text_file: text_file.write(entity_key) args = ['ssh-keygen', '-s', ca_file, '-I', 'testID', '-V', - '-1d:+365d'] - if hostname is None: + '-1d:+365d', '-z', str(serial)] + if user: args.extend(['-n', principals, pub_file]) else: args.extend(['-h', pub_file]) @@ -51,22 +51,19 @@ def generateCert(auth_key, entity_key, hostname=None, principals='root'): return cert -def revokedKeysBase64(auth_key, serial_list): +def revokedKeysBase64(ca_public, serial_list): # Temporarily write the authority private key and list of serials temp_dir = mkdtemp() - ca_file = '/'.join([temp_dir, 'ca_key']) + ca_file = '/'.join([temp_dir, 'ca_public']) serials_file = '/'.join([temp_dir, 'serials']) revoked_file = '/'.join([temp_dir, 'revoked']) try: - fd = os.open(ca_file, os.O_WRONLY | os.O_CREAT, 0o600) - os.close(fd) - with open(ca_file, "w") as text_file: - text_file.write(auth_key) + with open(ca_file, "w", 0o644) as text_file: + text_file.write(ca_public) with open(serials_file, "w", 0o644) as text_file: for s in serial_list: - text_file.write("serial: " + s + "\n") - args = ['ssh-keygen', '-s', ca_file, '-k', '-f', revoked_file, - serials_file] + text_file.write("serial: {}\n".format(s)) + args = ['ssh-keygen', '-k', '-f', revoked_file, '-s', ca_file, serials_file] subprocess.check_output(args, stderr=subprocess.STDOUT) # Return the base64 encoded contents of the revoked keys file with open(revoked_file, 'r') as text_file: