diff --git a/devstack/create_config b/devstack/create_config index 87634a39..f6703701 100755 --- a/devstack/create_config +++ b/devstack/create_config @@ -207,7 +207,8 @@ if [[ -n "$neutron_item" ]]; then fi wait $cirros_image_wget_pid -if [[ "$?" -eq "0" ]]; then +if [[ "$?" -eq "0" && "$CA_CERT" && -e "$CA_CERT" ]]; then + sudo apt-get -fy install ruby ID="$(cat /dev/urandom | tr -cd 'a-f0-9' | head -c 8)" WORKING_DIR="/tmp/bi-$ID" mkdir -p $WORKING_DIR @@ -219,16 +220,20 @@ if [[ "$?" -eq "0" ]]; then # IMPORTANT! bucket name should contain '.' - in this case ami-tools will not build s3 url with bucket name. AWS_AMI_BUCKET="tmp-bundle.$ID" - nova x509-get-root-cert $WORKING_DIR/cacert.pem - nova x509-create-cert $WORKING_DIR/pk.pem $WORKING_DIR/cert.pem + EC2_USER_ID=42424242424242 # ec2api does not use user id, but bundling requires it + EC2_PRIVATE_KEY="$WORKING_DIR/private/pk.pem" + EC2_CSR="$WORKING_DIR/cert.csr" + EC2_CERT="$WORKING_DIR/cert.pem" - export EC2_USER_ID=42424242424242 # ec2api does not use user id, but bundling requires it - export EC2_PRIVATE_KEY=$WORKING_DIR/pk.pem - export EC2_CERT=$WORKING_DIR/cert.pem - export NOVA_CERT=$WORKING_DIR/cacert.pem + mkdir -p "$WORKING_DIR/private/" + + # generate user certificate + openssl genrsa -out "$EC2_PRIVATE_KEY" 2048 + openssl req -new -key "$EC2_PRIVATE_KEY" -subj "/C=RU/ST=Moscow/L=Moscow/O=Progmatic/CN=functional-tests" -out "$EC2_CSR" + openssl x509 -req -in "$EC2_CSR" -CA "$CA_CERT" -CAkey "$CA_KEY" -CAcreateserial -out "$EC2_CERT" -days 365 mkdir -p "$IMAGES_DIR" - $TOOLS_DIR/bin/ec2-bundle-image --cert $EC2_CERT --privatekey $EC2_PRIVATE_KEY --ec2cert $NOVA_CERT --image /tmp/$CIRROS_IMAGE_FNAME --prefix $CIRROS_IMAGE_FNAME --user $EC2_USER_ID --destination "$IMAGES_DIR" --arch x86_64 + $TOOLS_DIR/bin/ec2-bundle-image --cert $EC2_CERT --privatekey $EC2_PRIVATE_KEY --ec2cert $CA_CERT --image /tmp/$CIRROS_IMAGE_FNAME --prefix $CIRROS_IMAGE_FNAME --user $EC2_USER_ID --destination "$IMAGES_DIR" --arch x86_64 if [[ "$?" -eq "0" ]]; then $TOOLS_DIR/bin/ec2-upload-bundle --url "$S3_URL" --access-key $ec2_access_key --secret-key $ec2_secret_key --bucket "$AWS_AMI_BUCKET" --manifest "$IMAGES_DIR/$CIRROS_IMAGE_FNAME.manifest.xml" --acl "public-read" --sigv 2 if [[ "$?" -eq "0" ]]; then diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 855b8498..9b20748a 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -154,6 +154,17 @@ function configure_ec2api_networking { iniset $EC2API_CONF_FILE DEFAULT disable_ec2_classic True } +function create_x509_server_key() { + export CA_KEY="$EC2API_STATE_PATH/private/ca_key.pem" + export CA_CERT="$EC2API_STATE_PATH/cacert.pem" + + mkdir -p "$EC2API_STATE_PATH/private/" + + # generate root certificate + openssl genrsa -out "$CA_KEY" 2048 + openssl req -x509 -new -key "$CA_KEY" -days 365 -out "$CA_CERT" -subj "/C=RU/ST=Moscow/L=Moscow/O=Progmatic/CN=ec2api-devstack" +} + # Entry points # ------------ @@ -221,6 +232,9 @@ function configure_ec2api { iniset $EC2API_CONF_FILE cache enabled True iniset $EC2API_CONF_FILE cache backend "$CACHE_BACKEND" + if create_x509_server_key; then + iniset $EC2API_CONF_FILE DEFAULT x509_root_private_key "$CA_KEY" + fi } diff --git a/ec2api/api/image.py b/ec2api/api/image.py index 12588700..60b6d3bf 100644 --- a/ec2api/api/image.py +++ b/ec2api/api/image.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import base64 import binascii import json import os @@ -23,6 +22,9 @@ import time import boto.s3.connection from cinderclient import exceptions as cinder_exception +from cryptography.hazmat import backends +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import serialization import eventlet from glanceclient.common import exceptions as glance_exception from lxml import etree @@ -60,6 +62,8 @@ s3_opts = [ default=False, help='Whether to affix the tenant id to the access key ' 'when downloading from S3'), + cfg.StrOpt('x509_root_private_key', + help='Path to ca private key file'), ] CONF = cfg.CONF @@ -969,14 +973,13 @@ def _s3_decrypt_image(context, encrypted_filename, encrypted_key, encrypted_iv, decrypted_filename): encrypted_key = binascii.a2b_hex(encrypted_key) encrypted_iv = binascii.a2b_hex(encrypted_iv) - cert_client = clients.nova_cert(context) try: - key = cert_client.decrypt_text(base64.b64encode(encrypted_key)) + key = _decrypt_text(encrypted_key) except Exception as exc: msg = _('Failed to decrypt private key: %s') % exc raise exception.EC2Exception(msg) try: - iv = cert_client.decrypt_text(base64.b64encode(encrypted_iv)) + iv = _decrypt_text(encrypted_iv) except Exception as exc: msg = _('Failed to decrypt initialization vector: %s') % exc raise exception.EC2Exception(msg) @@ -1030,3 +1033,15 @@ def _s3_conn(context): calling_format=calling, port=CONF.s3_port, host=CONF.s3_host) + + +def _decrypt_text(text): + private_key_file = CONF.x509_root_private_key + if not private_key_file: + msg = _("Path to ca private key isn't configured") + raise exception.EC2Exception(msg) + with open(private_key_file, 'rb') as f: + data = f.read() + priv_key = serialization.load_pem_private_key( + data, None, backends.default_backend()) + return priv_key.decrypt(text, padding.PKCS1v15()) diff --git a/ec2api/tests/unit/test_image.py b/ec2api/tests/unit/test_image.py index 7dce03c5..b1a3eb60 100644 --- a/ec2api/tests/unit/test_image.py +++ b/ec2api/tests/unit/test_image.py @@ -14,11 +14,13 @@ import json import os +import six import tempfile from cinderclient import exceptions as cinder_exception import eventlet import mock +from oslo_concurrency import processutils from ec2api.api import image as image_api from ec2api import exception @@ -908,3 +910,31 @@ class S3TestCase(base.BaseTestCase): exception.EC2InvalidException, image_api._s3_test_for_malicious_tarball, "/unused", os.path.join(os.path.dirname(__file__), 'rel.tar.gz')) + + def test_decrypt_text(self): + public_key = os.path.join(os.path.dirname(__file__), 'test_cert.pem') + private_key = os.path.join(os.path.dirname(__file__), + 'test_private_key.pem') + subject = "/C=RU/ST=Moscow/L=Moscow/O=Progmatic/CN=RootCA" + certificate_file = processutils.execute('openssl', + 'req', '-x509', '-new', + '-key', private_key, + '-days', '365', + '-out', public_key, + '-subj', subject) + text = "some @#!%^* test text" + process_input = text.encode("ascii") if six.PY3 else text + enc, _err = processutils.execute('openssl', + 'rsautl', + '-certin', + '-encrypt', + '-inkey', public_key, + process_input=process_input, + binary=True) + self.assertRaises(exception.EC2Exception, image_api._decrypt_text, enc) + self.configure(x509_root_private_key=private_key) + dec = image_api._decrypt_text(enc) + self.assertIsInstance(dec, bytes) + if six.PY3: + dec = dec.decode('ascii') + self.assertEqual(text, dec) diff --git a/ec2api/tests/unit/test_private_key.pem b/ec2api/tests/unit/test_private_key.pem new file mode 100644 index 00000000..b59d29c3 --- /dev/null +++ b/ec2api/tests/unit/test_private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA1+OySRkwliGtujemv7E4cqhJCxlF+4YULhmZaTWpmKUvKz5c +VC6wx/5A6Zz0XrZtuiFDgS7sIAGODcF18xpyKnp2Ign6zgCQpg0s+rss9q5vrSai +JR5iXdqA/vfemS9ptRyz6LSCI9O+EL/U7AaRHVBMQJEM2TfQeuowgi7XboKs8ljl +N4LpAu3HdtaFjHlBnegr7iJ2lq7rTj9deu1tJf/EbrLOclB2LwafT2kC0HQL2E0O +OphKu60qBR7sZ4M4tdUftCGZEAdd8+vvRd+YVK9TDtcYtSFSXh86a16504bPylCL +b/dF2bupLMe/zGLZIZ/hKbYPyfH6k5HFJIC12wIDAQABAoIBAH07F7BPbF+qKZxb +q96GbrgT5ksJ3g6JOCuFrffZqQdiynnLMsOiUemxEvZwlVBbgkr2ALJvBYmLXVud +XU4niRIa92vHXjUhHscz3WOUMADoLt/CCUx+05CdrzY3kmhJmIf2nmXeT594tEgC +/v/qz0Kx0YmimlFmjwi90GWzxkPTeKW1s8H9J0Xi/UX3urIfcCzRChumxY+f/sBq +CWJua3D6o3wx3B9oU0msC0R7ZIBY4PFhKyomcVyynD0HNe80NomyeRuJGm7NHHRB +MsVggZVgsFGkOZh00UkcaUHOKS+/7iyQAXuqms2amxGqLoxq/U11uoRW0NxOlC/7 +YYElIAECgYEA8cXXQjt2KWWNWIn2L0mWm+EGF/ihkvGmEXZVcuiVnuwyNxf+VAMe +f1i3EF+Hqlmyv46j4ReTbdpeW7bv7VW0iMgW68K3eFiPznBzlRA/n3XULAy41oyt +9kGhxzy6565iQMF0FK4viAuDeA6EwBkcu5MAQNOZXAUoGk02+GKPN6ECgYEA5Jfw +OeHcodteQdP0KEvChjGrZ7LNz3QU0kxvBaOjIg4ElEvJbq6EzSBxdugT7L+blDEU +umu8RgRn2Z73608nNtagBAKjg+VQsmnXxC92Ht1VFJF+uLLpmOSWG32Q/9e4nPUM +bFW9PyakF9LOM+lnqomKBhY1/LDAf77sWiQpS/sCgYAuh9n+2DzMiMvkP2EPBsWi +qHMox+QoyLMiZzjYzaSGGoUrj0WWW6dR8PwCfbA5e9vn/AbUOlpYaQ+B7TpN3hHJ +xWCL7USsN7ctjvzfsmncQawc8jHcsOSGIWmGU8zQ7AHi3ph9pmxlbXnW8ExiQDME +cq04zMCWMjPeo/+xXB6eIQKBgF+zUGobKdBFU6/BeY1JMlYWA0l1rP41/eWRBEXb +HRfLwJUJKXqB660o8Pez72uFSDABYEkvg3HYtFWCXQ6RY7xsnC8xn50/aspWz3Md +35jKVq02wFO4610MDd/ScNr7SBnF6X6NYp5GohorMhK/m5vk2vjzYYS5xs10c+TF +ENjzAoGAMH+O2S7LFjBKNuIpaqdIj8bjFwUhKgOG74/YodhxxgYIcWaHf2xnImuX +L/dam7seSo4MFYXX60lph6tutTyppkxeS64cz3X8cYN+CJQseWVestPkwArcF4m4 +M3mwEPCKoTqFUzaGbFbEl+TzEfQaKZwLn+AbsuMo6PBoAuD2fM4= +-----END RSA PRIVATE KEY----- diff --git a/requirements.txt b/requirements.txt index 80388ddc..87f0fb4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Babel!=2.4.0,>=2.3.4 # BSD boto>=2.32.1 # MIT botocore>=1.0.0 # Apache-2.0 +cryptography>=1.6 # BSD/Apache-2.0 eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT greenlet>=0.3.2 # MIT httplib2>=0.7.5 # MIT