Implemented certificate revocation.
This commit is contained in:
parent
b5991fe143
commit
8e52c850ce
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user