Implemented certificate revocation.

This commit is contained in:
Pino de Candia 2018-01-19 16:56:26 -06:00
parent b5991fe143
commit 8e52c850ce
8 changed files with 136 additions and 25 deletions

View File

@ -30,6 +30,7 @@ disable_service q-agt
# We have to disable the neutron dhcp agent. DF does not use the dhcp agent.
disable_service q-dhcp
ENABLE_AGING_APP=True
Q_ENABLE_DRAGONFLOW_LOCAL_CONTROLLER=True
DF_SELECTIVE_TOPO_DIST=False
DF_REDIS_PUBSUB=True

View File

@ -13,7 +13,6 @@ write_files:
import json
import requests
import os
import subprocess
import uuid
def getVendordataFromConfigDrive():
path = '/mnt/config/openstack/latest/vendor_data2.json'
@ -77,16 +76,38 @@ write_files:
f.write(hostcert['key-cert.pub'])
# Write the authorized principals file
os.mkdir('/etc/ssh/auth_principals')
with open('/etc/ssh/auth_principals/ubuntu', 'w') as f:
with open('/etc/ssh/auth_principals/root', 'w') as f:
for p in principals:
f.write(p + os.linesep)
# Write the User CA public key file
with open('/etc/ssh/ca_user.pub', 'w') as f:
f.write(tatu['auth_pub_key_user'])
print 'All tasks completed.'
- path: /root/manage-revoked_keys.py
permissions: '0700'
owner: root:root
content: |
import base64
import json
import requests
import uuid
path = '/mnt/config/openstack/latest/meta_data.json'
with open(path, 'r') as f:
json_string = f.read()
metadata = json.loads(json_string)
auth_id = str(uuid.UUID(metadata['project_id'], version=4))
response = requests.get(server + '/noauth/revokedkeys/' + auth_id)
assert response.status_code == 200
body = json.loads(response.content)
assert 'revoked_keys_data' in body
with open('/etc/ssh/revoked-keys', 'w') as f:
f.write(base64.b64decode(crl_body['revoked_keys_data']))
runcmd:
- dnf install -y python python-requests
- python /root/setup-ssh.py > /var/log/setup-ssh.log 2>&1
- sed -i -e '$aTrustedUserCAKeys /etc/ssh/ca_user.pub' /etc/ssh/sshd_config
- sed -i -e '$aAuthorizedPrincipalsFile /etc/ssh/auth_principals/%u' /etc/ssh/sshd_config
- sed -i -e '$aHostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub' /etc/ssh/sshd_config
- systemctl restart ssh
- python /root/manage-revoked-keys.py >> /var/log/setup-ssh.log 2>&1
- sed -i -e '$aRevokedKeys /etc/ssh/revoked-keys' /etc/ssh/sshd_config
- systemctl restart sshd

View File

@ -11,11 +11,8 @@
# under the License.
import falcon
import os.path
from oslo_config import cfg
from oslo_log import log as logging
import models
from tatu import config # sets up all required config
from tatu import models
from tatu.db.persistence import SQLAlchemySessionManager
LOG = logging.getLogger(__name__)
@ -31,6 +28,7 @@ def create_app(sa):
api.add_route('/hostcerts/{host_id}/{fingerprint}', models.HostCert())
api.add_route('/hosttokens', models.Tokens())
api.add_route('/novavendordata', models.NovaVendorData())
api.add_route('/revokeduserkeys/{auth_id}', models.RevokedUserKeys())
return api

View File

@ -124,6 +124,15 @@ class Authority(object):
resp.body = json.dumps(body)
resp.status = falcon.HTTP_OK
def _userAsDict(user):
return {
'user_id': user.user_id,
'fingerprint': user.fingerprint,
'auth_id': user.auth_id,
'key-cert.pub': user.cert,
'revoked': user.revoked,
'serial': user.serial,
}
class UserCerts(object):
@falcon.before(validate)
@ -146,12 +155,7 @@ class UserCerts(object):
users = db.getUserCerts(self.session)
items = []
for user in users:
items.append({
'user_id': user.user_id,
'fingerprint': user.fingerprint,
'auth_id': user.auth_id,
'key-cert.pub': user.cert,
})
items.append(_userAsDict(user))
body = {'users': items}
resp.body = json.dumps(body)
resp.status = falcon.HTTP_OK
@ -164,13 +168,7 @@ class UserCert(object):
if user is None:
resp.status = falcon.HTTP_NOT_FOUND
return
body = {
'user_id': user.user_id,
'fingerprint': user.fingerprint,
'auth_id': user.auth_id,
'key-cert.pub': user.cert,
}
resp.body = json.dumps(body)
resp.body = json.dumps(_userAsDict(user))
resp.status = falcon.HTTP_OK
@ -295,3 +293,25 @@ class NovaVendorData(object):
req.body['instance-id'], 22)
add_srv_records(req.body['hostname'], req.body['project-id'],
port_ip_tuples)
class RevokedUserKeys(object):
@falcon.before(validate)
def on_get(self, req, resp, auth_id):
body = {
'auth_id': auth_id,
'encoding': 'base64',
'revoked_keys_data': db.getRevokedKeysBase64(self.session, auth_id)
}
resp.body = json.dumps(body)
resp.status = falcon.HTTP_OK
@falcon.before(validate)
def on_post(self, req, resp, auth_id):
db.revokeUserKey(
self.session,
auth_id,
serial=req.body.get('serial', None),
key=req.body.get('key_id', None),
cert=req.body.get('cert', None)
)
resp.status = falcon.HTTP_OK

View File

@ -14,7 +14,6 @@ from castellan.common.objects.passphrase import Passphrase
from castellan.common.utils import credential_factory
from castellan.key_manager import API
from castellan.key_manager.key_manager import KeyManager
from oslo_config import cfg
from oslo_log import log as logging
from tatu.config import CONTEXT

View File

@ -19,7 +19,7 @@ from sqlalchemy.ext.declarative import declarative_base
import sshpubkeys
from tatu.castellano import get_secret, store_secret
from tatu.utils import generateCert, random_uuid
from tatu.utils import generateCert, revokedKeysBase64, random_uuid
Base = declarative_base()
@ -71,6 +71,8 @@ class UserCert(Base):
fingerprint = sa.Column(sa.String(60), primary_key=True)
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)
def getUserCert(session, user_id, fingerprint):
@ -107,6 +109,52 @@ def createUserCert(session, user_id, auth_id, pub):
return user
class RevokedKey(Base):
__tablename__ = 'revoked_keys'
auth_id = sa.Column(sa.String(36), primary_key=True)
serial = sa.Column(sa.Integer, sa.ForeignKey("user_certs.serial"),
primary_key=True)
def getRevokedKeysBase64(session, auth_id):
auth = getAuthority(session, auth_id)
if auth is None:
raise falcon.HTTPNotFound(
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)
def revokeUserKey(session, auth_id, serial=None, key_id=None, cert=None):
ser = None
userCert = None
if serial is not None:
userCert = session.query(UserCert).filter(
UserCert.serial == serial).one()
if userCert is None:
raise falcon.HTTPBadRequest(
"Can't find the certificate for serial # {}".format(serial))
if userCert.auth_id != auth_id:
raise falcon.HTTPBadRequest(
"Incorrect CA ID for serial # {}".format(serial))
ser = serial
elif key is not None:
# TODO(pino): look up the UserCert by key id and get the serial number
pass
elif cert is not None:
# TODO(pino): Decode the cert, validate against UserCert and get serial
pass
if ser is None or userCert is None:
raise falcon.HTTPBadRequest("Cannot identify which Cert to revoke.")
userCert.revoked = True
session.add(userCert)
session.add(RevokedKey(auth_id=auth_id, serial=ser))
session.commit()
class Token(Base):
__tablename__ = 'tokens'

View File

@ -10,7 +10,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslo_log import log as logging
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import base64
import os
import shutil
import subprocess
@ -23,7 +24,6 @@ def random_uuid():
def generateCert(auth_key, entity_key, hostname=None, principals='root'):
# Temporarily write the authority private key, entity public key to files
prefix = uuid.uuid4().hex
temp_dir = mkdtemp()
ca_file = '/'.join([temp_dir, 'ca_key'])
pub_file = '/'.join([temp_dir, 'entity.pub'])
@ -48,4 +48,29 @@ def generateCert(auth_key, entity_key, hostname=None, principals='root'):
cert = text_file.read()
finally:
shutil.rmtree(temp_dir)
return cert
return cert
def revokedKeysBase64(auth_key, serial_list):
# Temporarily write the authority private key and list of serials
temp_dir = mkdtemp()
ca_file = '/'.join([temp_dir, 'ca_key'])
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(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]
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:
b64data = base64.b64encode(text_file.read())
finally:
shutil.rmtree(temp_dir)
return b64data