diff --git a/.travis.yml b/.travis.yml index f3790d1..8eed579 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,10 @@ language: python python: - "2.7" + - "3.4" + - "3.5" + - "3.6" install: "pip install tox-travis" -script: tox -e py27-local -- -v +script: tox -- -v after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/HISTORY.rst b/HISTORY.rst index 5ef93e9..be5fa2b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,16 @@ Release History --------------- +2.0.0 (2018-04-11) +++++++++++++++++++ + +- Wrap the aws-encryption-sdk-cli adding: + - Windows support + - Compressed points support + - Output to stdout +- Add a --quiet argument to suppress all output +- Add a compatibility warning when the file mrcrypt is decrypting uses uncompressed points + 1.1.0 (2017-02-27) ++++++++++++++++++ diff --git a/README.rst b/README.rst index 506c3ba..f36c3d4 100644 --- a/README.rst +++ b/README.rst @@ -16,15 +16,10 @@ mrcrypt: Multi-Region Encryption mrcrypt is a command-line tool which encrypts secrets that conform to the AWS Encryption SDK's `message format `__ -for envelope encryption. Envelope encryption is used to encrypt a file using a -KMS data key. That data key is then encrypted with regional KMS Customer Master -Keys. Each regionally encrypted data key is then stored in the encrypted -message. When decrypting, the appropriate regional CMK is used to decrypt the -data key, and the data key is then used to decrypt the file. In other words, -encrypt once - decrypt from anywhere. +for envelope encryption. As of v2.0.0, mrcrypt now wraps the `aws-encryption-sdk-cli `__. -mrcrypt is compatible with the AWS Encryption SDK message format. For details, see the -section titled `Compatibility with the AWS Encryption SDK`_. +For more information about the AWS Encryption SDK see +``__. Installation ============ @@ -35,12 +30,6 @@ You can install the latest release of mrcrypt with `pip`: pip install mrcrypt -**Note:** mrcrypt uses the Python package -`Cryptography `__ which depends on -``libffi``. You may need to install it on your system if -``pip install .`` fails. For more specific instructions for your OS: -https://cryptography.io/en/latest/installation/ - Quick Start =========== @@ -61,8 +50,7 @@ Usage :: - usage: mrcrypt [-h] [-p PROFILE] [-e ENCRYPTION_CONTEXT] [-d] [-o OUTFILE] - {encrypt,decrypt} ... + usage: mrcrypt [-h] [-p PROFILE] [-v] [-q] [-o OUTFILE] {encrypt,decrypt} ... Multi Region Encryption. A tool for managing secrets across multiple AWS regions. @@ -74,11 +62,11 @@ Usage -h, --help show this help message and exit -p PROFILE, --profile PROFILE The profile to use - -e ENCRYPTION_CONTEXT, --encryption_context ENCRYPTION_CONTEXT - An encryption context to use. (Cannot have whitespace) - -d, --debug Enable more output for debugging + -v, --verbose More verbose output (ignored if --quiet) + -q, --quiet Quiet all output -o OUTFILE, --outfile OUTFILE - The file to write the results to + The file to write the results to (use "-" to write to + stdout Both the encrypt, and decrypt commands can encrypt and decrypt files in directories recursively. @@ -114,9 +102,17 @@ Output file name If you want to specify the output filename, you can use the ``-o`` argument. -``# Encrypt 'file.txt' writing the output into 'encrypted-file.txt' mrcrypt -o encrypted-file.txt encrypt alias/master-key file.txt`` +:: + # Encrypt 'file.txt' writing the output into 'encrypted-file.txt' + mrcrypt -o encrypted-file.txt encrypt alias/master-key file.txt + +To write to stdout, you can use `-` + +:: + # Encrypt 'file.txt' writing the output to stdout + mrcrypt -o - encrypt alias/master-key file.txt -When an output filename is not specified, mrcrypt will use the input +When the output filename argument is not specified, mrcrypt will use the input filename as a base and add a suffix. On encrypt this suffix is ``.encrypted`` and on decrypt this suffix is ``.decrypted``. @@ -132,7 +128,7 @@ Encryption positional arguments: key_id An identifier for a customer master key. - filename The file or directory to encrypt. Use a - to read from + filename The file or directory to encrypt. Use "-" to read from stdin optional arguments: @@ -163,7 +159,7 @@ Decryption Decrypts a file positional arguments: - filename The file or directory to decrypt. Use a - to read from stdin + filename The file or directory to decrypt. Use "-" to read from stdin optional arguments: -h, --help show this help message and exit @@ -183,9 +179,21 @@ Testing Running tests for mrcrypt is easy if you have ``tox`` installed. Simply run ``tox`` at the project's root. -Compatibility with the AWS Encryption SDK -========================================= +If you have an AWS account with a KMS key, you can run the integration tests using -**From v1.2.0 on, all files encrypted with mrcrypt can be decrypted with any AWS Encryption -SDK client.** v1.2.0+ is backwards compatible with files generated by earlier versions of, -but earlier versions of mrcrypt cannot decrypt files generated by v1.2.0+. +:: + + AWS_ENCRYPTION_SDK_PYTHON_INTEGRATION_TEST_AWS_KMS_KEY_ID= tox -e py{27,34,35,36}-{local,integ} + +Note about files created with mrcrypt before v2.0.0 +=================================================== + +Upon the release of v2.0.0, mrcrypt started wrapping the +`aws-encryption-sdk-cli `__. Wrapping the +aws-encryption-sdk-cli means that mrcrypt now fully conforms to the AWS +Encryption SDK's `message format +`__ and uses +compressed points when encrypting files. Before v2.0.0, mrcrypt did not use compressed points, and +while still secure, it lead to compatibility issues with other AWS Encryption SDK implementations. +To update your pre-2.0 mrcrypt encrypted files, and improve compatibility with the AWS Encryption +SDK, simply decrypt and re-encrypt your file with the latest version of mrcrypt. diff --git a/mrcrypt/cli/parser.py b/mrcrypt/cli/parser.py index 75008fd..4300d75 100644 --- a/mrcrypt/cli/parser.py +++ b/mrcrypt/cli/parser.py @@ -34,8 +34,7 @@ def _build_encrypt_parser(subparsers): encrypt_parser.add_argument('filename', action='store', - help='The file or directory to encrypt. Use a - to read from ' - 'stdin') + help='The file or directory to encrypt. Use "-" to read from stdin') def _build_decrypt_parser(subparsers): @@ -45,8 +44,7 @@ def _build_decrypt_parser(subparsers): decrypt_parser.add_argument('filename', action='store', - help='The file or directory to decrypt. Use a - to read from ' - 'stdin') + help='The file or directory to decrypt. Use "-" to read from stdin') def _build_parser(): @@ -57,8 +55,10 @@ def _build_parser(): parser.add_argument('-p', '--profile', action='store', help='The profile to use') parser.add_argument('-v', '--verbose', action='count', - help='More verbose output') - parser.add_argument('-o', '--outfile', action='store', help='The file to write the results to') + help='More verbose output (ignored if --quiet)') + parser.add_argument('-q', '--quiet', action='store_true', help='Quiet all output') + parser.add_argument('-o', '--outfile', action='store', + help='The file to write the results to (use "-" to write to stdout') subparsers = parser.add_subparsers(dest='command') @@ -175,9 +175,18 @@ def parse(raw_args=None): if mrcrypt_args.encryption_context is not None and not isinstance(mrcrypt_args.encryption_context, dict): return 'Invalid dictionary in encryption context argument' + # setup mrcrypt's logger + if mrcrypt_args.quiet: + log_level = logging.CRITICAL + else: + log_level = logging.WARN if not mrcrypt_args.verbose else logging.DEBUG + + _LOGGER.setLevel(log_level) + + # setup aws_encryption_sdk's logger aws_encryption_sdk_cli.setup_logger( verbosity=mrcrypt_args.verbose, - quiet=False + quiet=mrcrypt_args.quiet ) encryption_cli_args = _transform_args(mrcrypt_args) diff --git a/mrcrypt/materials_manager.py b/mrcrypt/materials_manager.py index d0faffe..8d3a1a1 100644 --- a/mrcrypt/materials_manager.py +++ b/mrcrypt/materials_manager.py @@ -71,6 +71,9 @@ def decrypt_materials(self, request): # Once this issue is addressed, the caught exception classes should be narrowed appropriately: # https://github.com/awslabs/aws-encryption-sdk-python/issues/21 + _LOGGER.warning("This file is encrypted using an uncompressed key, which may lead to compatibility issues " + "with the AWS Encryption SDK.") + data_key = self.master_key_provider.decrypt_data_key_from_list( # subclasses confuse pylint: disable=no-member encrypted_data_keys=request.encrypted_data_keys, algorithm=request.algorithm, diff --git a/requirements.txt b/requirements.txt index f9c6bef..97d2255 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,31 +1,26 @@ -boto==2.42.0 -boto3==1.3.1 -botocore==1.4.62 -cffi==1.8.3 -click==6.6 -cryptography==1.5.2 -docutils==0.12 +asn1crypto==0.24.0 +attrs==17.4.0 +aws-encryption-sdk==1.3.3 +aws-encryption-sdk-cli==1.1.4 +boto3==1.7.3 +botocore==1.10.3 +cffi==1.11.5 +cryptography==2.2.2 +docutils==0.14 enum34==1.1.6 -Flask==0.11.1 -futures==3.0.5 -httpretty==0.8.10 -idna==2.1 -ipaddress==1.0.17 -itsdangerous==0.24 -Jinja2==2.8 -jmespath==0.9.0 -MarkupSafe==0.23 --e git+https://github.com/austinmoore-/moto.git@405d8c63b4b735a41aa4938675506fe40517bfa3#egg=moto -pluggy==0.3.1 -py==1.4.31 -pyasn1==0.1.9 -pycparser==2.14 -pytest==2.9.2 -pytest-cov==2.4.0 -python-dateutil==2.5.3 -requests==2.11.1 -six==1.10.0 -tox==2.3.1 -virtualenv==15.0.3 -Werkzeug==0.11.11 -xmltodict==0.10.2 +funcsigs==1.0.2 +futures==3.2.0 +idna==2.6 +ipaddress==1.0.19 +jmespath==0.9.3 +more-itertools==4.1.0 +-e git+https://github.com/austinmoore-/mrcrypt.git@21b5f6213a5496a34eaa40cc2c81c5d745789eb7#egg=mrcrypt +pluggy==0.6.0 +py==1.5.3 +pycparser==2.18 +pytest==3.5.0 +python-dateutil==2.6.1 +s3transfer==0.1.13 +six==1.11.0 +typing==3.6.4 +wrapt==1.10.11 diff --git a/setup.py b/setup.py index b19deb9..1c2c494 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='mrcrypt', - version='1.2.0', + version='2.0.0', description='A command-line tool that can encrypt/decrypt secrets using the AWS Encryption SDK ' 'for use in multiple AWS KMS regions.', long_description=readme, @@ -22,6 +22,8 @@ 'Intended Audience :: System Administrators', 'Natural Language :: English', 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', diff --git a/tests/test_cli.py b/tests/test_cli.py index c2f132f..6bd0aab 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7,7 +7,7 @@ def test_encrypt_arg__all(): - arguments = ('--profile default -vv --outfile outfile.txt encrypt ' + arguments = ('--profile default -q -vv --outfile outfile.txt encrypt ' '--encryption_context {"1":"1"} --regions us-east-1 -- ' 'alias/test-key secrets.txt') args = parser._build_parser().parse_args(arguments.split()) @@ -20,6 +20,7 @@ def test_encrypt_arg__all(): assert args.outfile == 'outfile.txt' assert args.key_id == 'alias/test-key' assert args.command == 'encrypt' + assert args.quiet == True def test_encrypt_arg__multiple_regions(): @@ -34,6 +35,7 @@ def test_encrypt_arg__multiple_regions(): assert args.encryption_context is None assert args.profile is None assert args.outfile is None + assert args.quiet == False def test_encrypt_arg__minimum_args(): @@ -48,10 +50,11 @@ def test_encrypt_arg__minimum_args(): assert args.encryption_context is None assert args.regions is None assert args.outfile is None + assert args.quiet == False def test_decrypt_arg__all(): - arguments = '--profile default --outfile outfile.txt -vv decrypt secrets.txt' + arguments = '--profile default --outfile outfile.txt -q -vv decrypt secrets.txt' args = parser._build_parser().parse_args(arguments.split()) assert args.command == 'decrypt' @@ -59,6 +62,7 @@ def test_decrypt_arg__all(): assert args.outfile == 'outfile.txt' assert args.profile == 'default' assert args.verbose == 2 + assert args.quiet == True def test_decrypt_arg__minimum(): @@ -70,3 +74,4 @@ def test_decrypt_arg__minimum(): assert args.verbose is None assert args.profile is None assert args.outfile is None + assert args.quiet == False diff --git a/tox.ini b/tox.ini index 11d36d3..b681a1a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,6 @@ [tox] envlist = py{27,34,35,36}-local -[testenv:base-command] -commands = py.test -l --cov=mrcrypt --cov-report=term --cov-report=xml [] {posargs} - [testenv] usedevelop = true passenv = @@ -14,15 +11,22 @@ passenv = setenv = AWS_DEFAULT_REGION = us-east-1 commands = - local: {[testenv:base-command]commands} -m local - integ: {[testenv:base-command]commands} -m integ - all: {[testenv:base-command]commands} + local: pytest -l --cov=mrcrypt --cov-report=term --cov-report=xml -m local {posargs} + integ: pytest -l --cov=mrcrypt --cov-report=term --cov-report=xml -m integ {posargs} + all: pytest -l --cov=mrcrypt --cov-report=term --cov-report=xml {posargs} deps = pytest pytest-cov mock git+https://github.com/austinmoore-/moto.git@405d8c63b4b735a41aa4938675506fe40517bfa3#egg=moto +[travis] +python = + 2.7: py27-local + 3.4: py34-local + 3.5: py35-local + 3.6: py36-local + # Linters [testenv:flake8] basepython = python3