Skip to content

Commit

Permalink
Test AWSSigner with localstack
Browse files Browse the repository at this point in the history
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 secure-systems-lab#612

Signed-off-by: Lukas Puehringer <[email protected]>
  • Loading branch information
lukpueh committed Apr 11, 2024
1 parent fdaa97b commit 94d58a6
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 57 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/test-kms-aws.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions requirements-aws.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
boto3==1.34.82
botocore==1.34.82
116 changes: 59 additions & 57 deletions tests/check_aws_signer.py
Original file line number Diff line number Diff line change
@@ -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__":
Expand Down
27 changes: 27 additions & 0 deletions tests/scripts/init-aws-kms.sh
Original file line number Diff line number Diff line change
@@ -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)
35 changes: 35 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 94d58a6

Please sign in to comment.