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.
|
# We have to disable the neutron dhcp agent. DF does not use the dhcp agent.
|
||||||
disable_service q-dhcp
|
disable_service q-dhcp
|
||||||
|
|
||||||
|
ENABLE_AGING_APP=True
|
||||||
Q_ENABLE_DRAGONFLOW_LOCAL_CONTROLLER=True
|
Q_ENABLE_DRAGONFLOW_LOCAL_CONTROLLER=True
|
||||||
DF_SELECTIVE_TOPO_DIST=False
|
DF_SELECTIVE_TOPO_DIST=False
|
||||||
DF_REDIS_PUBSUB=True
|
DF_REDIS_PUBSUB=True
|
||||||
|
@ -13,7 +13,6 @@ write_files:
|
|||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import uuid
|
import uuid
|
||||||
def getVendordataFromConfigDrive():
|
def getVendordataFromConfigDrive():
|
||||||
path = '/mnt/config/openstack/latest/vendor_data2.json'
|
path = '/mnt/config/openstack/latest/vendor_data2.json'
|
||||||
@ -77,16 +76,38 @@ write_files:
|
|||||||
f.write(hostcert['key-cert.pub'])
|
f.write(hostcert['key-cert.pub'])
|
||||||
# Write the authorized principals file
|
# Write the authorized principals file
|
||||||
os.mkdir('/etc/ssh/auth_principals')
|
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:
|
for p in principals:
|
||||||
f.write(p + os.linesep)
|
f.write(p + os.linesep)
|
||||||
# Write the User CA public key file
|
# Write the User CA public key file
|
||||||
with open('/etc/ssh/ca_user.pub', 'w') as f:
|
with open('/etc/ssh/ca_user.pub', 'w') as f:
|
||||||
f.write(tatu['auth_pub_key_user'])
|
f.write(tatu['auth_pub_key_user'])
|
||||||
print 'All tasks completed.'
|
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:
|
runcmd:
|
||||||
|
- dnf install -y python python-requests
|
||||||
- python /root/setup-ssh.py > /var/log/setup-ssh.log 2>&1
|
- 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 '$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 '$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
|
- 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.
|
# under the License.
|
||||||
|
|
||||||
import falcon
|
import falcon
|
||||||
import os.path
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import models
|
from tatu import models
|
||||||
from tatu import config # sets up all required config
|
|
||||||
from tatu.db.persistence import SQLAlchemySessionManager
|
from tatu.db.persistence import SQLAlchemySessionManager
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -31,6 +28,7 @@ def create_app(sa):
|
|||||||
api.add_route('/hostcerts/{host_id}/{fingerprint}', models.HostCert())
|
api.add_route('/hostcerts/{host_id}/{fingerprint}', models.HostCert())
|
||||||
api.add_route('/hosttokens', models.Tokens())
|
api.add_route('/hosttokens', models.Tokens())
|
||||||
api.add_route('/novavendordata', models.NovaVendorData())
|
api.add_route('/novavendordata', models.NovaVendorData())
|
||||||
|
api.add_route('/revokeduserkeys/{auth_id}', models.RevokedUserKeys())
|
||||||
return api
|
return api
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,6 +124,15 @@ class Authority(object):
|
|||||||
resp.body = json.dumps(body)
|
resp.body = json.dumps(body)
|
||||||
resp.status = falcon.HTTP_OK
|
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):
|
class UserCerts(object):
|
||||||
@falcon.before(validate)
|
@falcon.before(validate)
|
||||||
@ -146,12 +155,7 @@ class UserCerts(object):
|
|||||||
users = db.getUserCerts(self.session)
|
users = db.getUserCerts(self.session)
|
||||||
items = []
|
items = []
|
||||||
for user in users:
|
for user in users:
|
||||||
items.append({
|
items.append(_userAsDict(user))
|
||||||
'user_id': user.user_id,
|
|
||||||
'fingerprint': user.fingerprint,
|
|
||||||
'auth_id': user.auth_id,
|
|
||||||
'key-cert.pub': user.cert,
|
|
||||||
})
|
|
||||||
body = {'users': items}
|
body = {'users': items}
|
||||||
resp.body = json.dumps(body)
|
resp.body = json.dumps(body)
|
||||||
resp.status = falcon.HTTP_OK
|
resp.status = falcon.HTTP_OK
|
||||||
@ -164,13 +168,7 @@ class UserCert(object):
|
|||||||
if user is None:
|
if user is None:
|
||||||
resp.status = falcon.HTTP_NOT_FOUND
|
resp.status = falcon.HTTP_NOT_FOUND
|
||||||
return
|
return
|
||||||
body = {
|
resp.body = json.dumps(_userAsDict(user))
|
||||||
'user_id': user.user_id,
|
|
||||||
'fingerprint': user.fingerprint,
|
|
||||||
'auth_id': user.auth_id,
|
|
||||||
'key-cert.pub': user.cert,
|
|
||||||
}
|
|
||||||
resp.body = json.dumps(body)
|
|
||||||
resp.status = falcon.HTTP_OK
|
resp.status = falcon.HTTP_OK
|
||||||
|
|
||||||
|
|
||||||
@ -295,3 +293,25 @@ class NovaVendorData(object):
|
|||||||
req.body['instance-id'], 22)
|
req.body['instance-id'], 22)
|
||||||
add_srv_records(req.body['hostname'], req.body['project-id'],
|
add_srv_records(req.body['hostname'], req.body['project-id'],
|
||||||
port_ip_tuples)
|
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.common.utils import credential_factory
|
||||||
from castellan.key_manager import API
|
from castellan.key_manager import API
|
||||||
from castellan.key_manager.key_manager import KeyManager
|
from castellan.key_manager.key_manager import KeyManager
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from tatu.config import CONTEXT
|
from tatu.config import CONTEXT
|
||||||
|
@ -19,7 +19,7 @@ from sqlalchemy.ext.declarative import declarative_base
|
|||||||
import sshpubkeys
|
import sshpubkeys
|
||||||
|
|
||||||
from tatu.castellano import get_secret, store_secret
|
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()
|
Base = declarative_base()
|
||||||
|
|
||||||
@ -71,6 +71,8 @@ class UserCert(Base):
|
|||||||
fingerprint = sa.Column(sa.String(60), primary_key=True)
|
fingerprint = sa.Column(sa.String(60), primary_key=True)
|
||||||
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
auth_id = sa.Column(sa.String(36), sa.ForeignKey('authorities.auth_id'))
|
||||||
cert = sa.Column(sa.Text)
|
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):
|
def getUserCert(session, user_id, fingerprint):
|
||||||
@ -107,6 +109,52 @@ def createUserCert(session, user_id, auth_id, pub):
|
|||||||
return user
|
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):
|
class Token(Base):
|
||||||
__tablename__ = 'tokens'
|
__tablename__ = 'tokens'
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import base64
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -23,7 +24,6 @@ def random_uuid():
|
|||||||
|
|
||||||
def generateCert(auth_key, entity_key, hostname=None, principals='root'):
|
def generateCert(auth_key, entity_key, hostname=None, principals='root'):
|
||||||
# Temporarily write the authority private key, entity public key to files
|
# Temporarily write the authority private key, entity public key to files
|
||||||
prefix = uuid.uuid4().hex
|
|
||||||
temp_dir = mkdtemp()
|
temp_dir = mkdtemp()
|
||||||
ca_file = '/'.join([temp_dir, 'ca_key'])
|
ca_file = '/'.join([temp_dir, 'ca_key'])
|
||||||
pub_file = '/'.join([temp_dir, 'entity.pub'])
|
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()
|
cert = text_file.read()
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(temp_dir)
|
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…
x
Reference in New Issue
Block a user