From 94d58a6c9a3addc9afe7c7010c4628c680af6f9b Mon Sep 17 00:00:00 2001 From: Lukas Puehringer Date: Thu, 11 Apr 2024 16:39:22 +0200 Subject: [PATCH] Test AWSSigner with localstack Add test setup to test AWSSigner locally or on GitHub CI using localstack. **Change details** * Add independent tox environment to init/cleanup localstack, configure ambient AWS KMS credentials, create test keys, and run the test. * Adds aws requirements file with pinned runtime requirements to trigger tox run via Dependabot. Note: test requirements are not included, but defined directly in tox.ini without pinning (we don't really need to trigger of those update). * Adds shell script to generate test keys for all supported schemes in tox setup. * Update AWSSigner tests to match localstack setup. The previous setup looked like it needed manual intervention in order to run. The new tests runs the full Signer API flow -- import (with and without passed scheme), load, sign, verify (pass and fail) -- automatically and for each scheme supported by AWSSigner. * Adds independent GitHub Action workflow to run tox aws test closes #612 Signed-off-by: Lukas Puehringer --- .github/workflows/test-kms-aws.yml | 27 +++++++ requirements-aws.txt | 2 + tests/check_aws_signer.py | 116 +++++++++++++++-------------- tests/scripts/init-aws-kms.sh | 27 +++++++ tox.ini | 35 +++++++++ 5 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/test-kms-aws.yml create mode 100644 requirements-aws.txt create mode 100755 tests/scripts/init-aws-kms.sh diff --git a/.github/workflows/test-kms-aws.yml b/.github/workflows/test-kms-aws.yml new file mode 100644 index 00000000..ab71929f --- /dev/null +++ b/.github/workflows/test-kms-aws.yml @@ -0,0 +1,27 @@ +name: Run AWS KMS tests + +on: + push: + pull_request: + +jobs: + local-aws-kms: + runs-on: ubuntu-latest + steps: + - name: Checkout securesystemslib + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 + + - name: Set up Python + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d + with: + python-version: '3.x' + cache: 'pip' + cache-dependency-path: 'requirements*.txt' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade tox + + - name: Run tests + run: tox -e local-aws-kms diff --git a/requirements-aws.txt b/requirements-aws.txt new file mode 100644 index 00000000..244a6744 --- /dev/null +++ b/requirements-aws.txt @@ -0,0 +1,2 @@ +boto3==1.34.82 +botocore==1.34.82 diff --git a/tests/check_aws_signer.py b/tests/check_aws_signer.py index 66bbdcc7..8668e73c 100644 --- a/tests/check_aws_signer.py +++ b/tests/check_aws_signer.py @@ -1,66 +1,68 @@ -"""This module confirms that signing using AWS KMS keys works. +"""Test AWSSigner -The purpose is to do a smoke test, not to exhaustively test every possible key -and environment combination. - -For AWS, the requirements to successfully test are: -* AWS authentication details -have to be available in the environment -* The key defined in the test has to be -available to the authenticated user - -Remember to replace the REDACTED fields to include the necessary values: -* keyid: Hash of the public key -* public: The public key, refer to other KMS tests to see the format -* aws_id: AWS KMS ID or alias """ import unittest from securesystemslib.exceptions import UnverifiedSignatureError -from securesystemslib.signer import AWSSigner, Key, Signer - - -class TestAWSKMSKeys(unittest.TestCase): - """Test that AWS KMS keys can be used to sign.""" - - pubkey = Key.from_dict( - "REDACTED", - { - "keytype": "rsa", - "scheme": "rsassa-pss-sha256", - "keyval": { - "public": "-----BEGIN PUBLIC KEY-----\nREDACTED\n-----END PUBLIC KEY-----\n" - }, - }, - ) - aws_key_id = "REDACTED" - - def test_aws_sign(self): - """Test that AWS KMS key works for signing""" - - data = "data".encode("utf-8") - - signer = Signer.from_priv_key_uri( - f"awskms:{self.aws_key_id}", self.pubkey - ) - sig = signer.sign(data) - - self.pubkey.verify_signature(sig, data) - with self.assertRaises(UnverifiedSignatureError): - self.pubkey.verify_signature(sig, b"NOT DATA") - - def test_aws_import_with_scheme(self): - """Test that AWS KMS key can be imported with a specified scheme.""" - uri, key = AWSSigner.import_(self.aws_key_id, self.pubkey.scheme) - self.assertEqual(key.keytype, self.pubkey.keytype) - self.assertEqual(uri, f"awskms:{self.aws_key_id}") - - def test_aws_import_without_scheme(self): - """Test that AWS KMS key can be imported without specifying a scheme.""" - uri, key = AWSSigner.import_(self.aws_key_id) - self.assertEqual(key.keytype, self.pubkey.keytype) - self.assertEqual(uri, f"awskms:{self.aws_key_id}") +from securesystemslib.signer import AWSSigner, Signer + + +class TestAWSSigner(unittest.TestCase): + """Test AWSSigner""" + + def test_aws_import_sign_verify(self): + # Test full signer flow with localstack + # - see tests/scripts/init-aws-kms.sh for how keys are created + # - see tox.ini for how credentials etc. are passed via env vars + keys_and_schemes = [ + ( + "alias/rsa", + "rsassa-pss-sha256", + [ + "rsassa-pss-sha256", + "rsassa-pss-sha384", + "rsassa-pss-sha512", + "rsa-pkcs1v15-sha256", + "rsa-pkcs1v15-sha384", + "rsa-pkcs1v15-sha512", + ], + ), + ( + "alias/ecdsa_nistp256", + "ecdsa-sha2-nistp256", + ["ecdsa-sha2-nistp256"], + ), + ( + "alias/ecdsa_nistp384", + "ecdsa-sha2-nistp384", + ["ecdsa-sha2-nistp384"], + ), + ] + for aws_keyid, default_scheme, schemes in keys_and_schemes: + for scheme in schemes: + # Test import + uri, public_key = AWSSigner.import_(aws_keyid, scheme) + self.assertEqual(uri, f"{AWSSigner.SCHEME}:{aws_keyid}") + self.assertEqual(scheme, public_key.scheme) + + # Test import with default_scheme + if scheme == default_scheme: + uri2, public_key2 = AWSSigner.import_(aws_keyid) + self.assertEqual(uri, uri2) + self.assertEqual(public_key, public_key2) + + # Test load + signer = Signer.from_priv_key_uri(uri, public_key) + self.assertIsInstance(signer, AWSSigner) + + # Test sign and verify + signature = signer.sign(b"DATA") + self.assertIsNone( + public_key.verify_signature(signature, b"DATA") + ) + with self.assertRaises(UnverifiedSignatureError): + public_key.verify_signature(signature, b"NOT DATA") if __name__ == "__main__": diff --git a/tests/scripts/init-aws-kms.sh b/tests/scripts/init-aws-kms.sh new file mode 100755 index 00000000..3df04552 --- /dev/null +++ b/tests/scripts/init-aws-kms.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Create test keys +awslocal kms create-key \ + --key-spec RSA_4096 \ + --key-usage SIGN_VERIFY + +awslocal kms create-key \ + --key-spec ECC_NIST_P256 \ + --key-usage SIGN_VERIFY + +awslocal kms create-key \ + --key-spec ECC_NIST_P384 \ + --key-usage SIGN_VERIFY + +# Create test keyid aliases ("alias/" prefix is mandatory) +awslocal kms create-alias \ + --alias-name alias/rsa \ + --target-key-id $(awslocal kms list-keys --query "Keys[0].KeyId" --output text) + +awslocal kms create-alias \ + --alias-name alias/ecdsa_nistp256 \ + --target-key-id $(awslocal kms list-keys --query "Keys[1].KeyId" --output text) + +awslocal kms create-alias \ + --alias-name alias/ecdsa_nistp384 \ + --target-key-id $(awslocal kms list-keys --query "Keys[2].KeyId" --output text) diff --git a/tox.ini b/tox.ini index 18952fbf..e6b2996b 100644 --- a/tox.ini +++ b/tox.ini @@ -78,3 +78,38 @@ commands = pylint -j 0 --rcfile=pylintrc securesystemslib tests bandit --recursive securesystemslib --exclude _vendor mypy + +# Requires docker running +[testenv:local-aws-kms] +deps = + -r{toxinidir}/requirements-pinned.txt + -r{toxinidir}/requirements-aws.txt + localstack + awscli + awscli-local + +allowlist_externals = + localstack + bash + +setenv = + AWS_ACCESS_KEY_ID = test + AWS_SECRET_ACCESS_KEY = test + AWS_ENDPOINT_URL = http://localhost:4566/ + AWS_DEFAULT_REGION = us-east-1 + +commands_pre = + # Start virtual AWS KMS + localstack start --detached + localstack wait + + # Create test keys + bash {toxinidir}/tests/scripts/init-aws-kms.sh + +commands = + # Run tests + python -m tests.check_aws_signer + +commands_post = + # Stop virtual AWS KMS + localstack stop