forked from secure-systems-lab/securesystemslib
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds fully functional VaultSigner and basic "import_ -> from_priv_key_uri -> sign -> verify" test loop using a local vault server. Tests run on CI and can be run locally with tox, if vault is installed. Note also that vault supports all key types and signing schemes supported by SSlibKey. The here added VaultSigner, however, only supports `ed25519` keys. Other key types and schemes may be added in follow-up PRs. Signed-off-by: Lukas Puehringer <[email protected]>
- Loading branch information
Showing
10 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
name: Run HashiCorp Vault tests | ||
|
||
on: | ||
push: | ||
pull_request: | ||
|
||
jobs: | ||
local-vault: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout securesystemslib | ||
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d | ||
with: | ||
python-version: '3.x' | ||
cache: 'pip' | ||
cache-dependency-path: 'requirements*.txt' | ||
|
||
- name: Install system dependencies | ||
shell: bash | ||
run: | | ||
sudo apt update && sudo apt install -y gpg wget | ||
wget -O- https://apt.releases.hashicorp.com/gpg | \ | ||
sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg | ||
gpg --no-default-keyring --fingerprint \ | ||
--keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg | ||
echo "deb [arch=$(dpkg --print-architecture) \ | ||
signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ | ||
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ | ||
sudo tee /etc/apt/sources.list.d/hashicorp.list | ||
sudo apt update && sudo apt install -y vault | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install --upgrade tox | ||
- name: Run tests | ||
run: tox -e local-vault |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
hvac==2.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
"""Signer implementation for HashiCorp Vault (Transit secrets engine)""" | ||
|
||
from base64 import b64decode, b64encode | ||
from typing import Optional, Tuple | ||
from urllib import parse | ||
|
||
from securesystemslib.exceptions import UnsupportedLibraryError | ||
from securesystemslib.signer._key import Key, SSlibKey | ||
from securesystemslib.signer._signer import SecretsHandler, Signature, Signer | ||
|
||
VAULT_IMPORT_ERROR = None | ||
try: | ||
import hvac | ||
from cryptography.hazmat.primitives.asymmetric.ed25519 import ( | ||
Ed25519PublicKey, | ||
) | ||
|
||
except ImportError: | ||
VAULT_IMPORT_ERROR = ( | ||
"Signing with HashiCorp Vault requires hvac and cryptography." | ||
) | ||
|
||
|
||
class VaultSigner(Signer): | ||
"""Signer for HashiCorp Vault Transit secrets engine | ||
The signer uses "ambient" credentials to connect to vault, most notably | ||
the environment variables ``VAULT_ADDR`` and ``VAULT_TOKEN`` must be set: | ||
https://developer.hashicorp.com/vault/docs/commands#environment-variables | ||
Priv key uri format is: ``hv:<KEY NAME>/<KEY VERSION>``. | ||
Arguments: | ||
hv_key_name: Name of vault key used for signing. | ||
public_key: Related public key instance. | ||
hv_key_version: Version of vault key used for signing. | ||
Raises: | ||
UnsupportedLibraryError: hvac or cryptography are not installed. | ||
""" | ||
|
||
SCHEME = "hv" | ||
|
||
def __init__(self, hv_key_name: str, public_key: Key, hv_key_version: int): | ||
if VAULT_IMPORT_ERROR: | ||
raise UnsupportedLibraryError(VAULT_IMPORT_ERROR) | ||
|
||
self.hv_key_name = hv_key_name | ||
self._public_key = public_key | ||
self.hv_key_version = hv_key_version | ||
|
||
# Client caches ambient settings in __init__. This means settings are | ||
# stable for subsequent calls to sign, also if the environment changes. | ||
self._client = hvac.Client() | ||
|
||
def sign(self, payload: bytes) -> Signature: | ||
"""Signs payload with HashiCorp Vault Transit secrets engine. | ||
Arguments: | ||
payload: bytes to be signed. | ||
Raises: | ||
Various errors from hvac. | ||
Returns: | ||
Signature. | ||
""" | ||
resp = self._client.secrets.transit.sign_data( | ||
self.hv_key_name, | ||
hash_input=b64encode(payload).decode(), | ||
key_version=self.hv_key_version, | ||
) | ||
|
||
sig_b64 = resp["data"]["signature"].split(":")[2] | ||
sig = b64decode(sig_b64).hex() | ||
|
||
return Signature(self.public_key.keyid, sig) | ||
|
||
@property | ||
def public_key(self) -> Key: | ||
return self._public_key | ||
|
||
@classmethod | ||
def from_priv_key_uri( | ||
cls, | ||
priv_key_uri: str, | ||
public_key: Key, | ||
secrets_handler: Optional[SecretsHandler] = None, | ||
) -> "VaultSigner": | ||
uri = parse.urlparse(priv_key_uri) | ||
|
||
if uri.scheme != cls.SCHEME: | ||
raise ValueError(f"VaultSigner does not support {priv_key_uri}") | ||
|
||
name, version = uri.path.split("/") | ||
|
||
return cls(name, public_key, int(version)) | ||
|
||
@classmethod | ||
def import_(cls, hv_key_name: str) -> Tuple[str, Key]: | ||
"""Load key and signer details from HashiCorp Vault. | ||
If multiple keys exist in the vault under the passed name, only the | ||
newest key is returned. Supported key type is: ed25519 | ||
See class documentation for details about settings and uri format. | ||
Arguments: | ||
hv_key_name: Name of vault key to import. | ||
Raises: | ||
UnsupportedLibraryError: hvac or cryptography are not installed. | ||
Various errors from hvac. | ||
Returns: | ||
Private key uri and public key. | ||
""" | ||
if VAULT_IMPORT_ERROR: | ||
raise UnsupportedLibraryError(VAULT_IMPORT_ERROR) | ||
|
||
client = hvac.Client() | ||
resp = client.secrets.transit.read_key(hv_key_name) | ||
|
||
# Pick key with highest version number | ||
version, key_info = sorted(resp["data"]["keys"].items())[-1] | ||
|
||
crypto_key = Ed25519PublicKey.from_public_bytes( | ||
b64decode(key_info["public_key"]) | ||
) | ||
|
||
key = SSlibKey.from_crypto(crypto_key) | ||
uri = f"{VaultSigner.SCHEME}:{hv_key_name}/{version}" | ||
|
||
return uri, key |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"""Test VaultSigner | ||
""" | ||
|
||
import unittest | ||
|
||
from securesystemslib.exceptions import UnverifiedSignatureError | ||
from securesystemslib.signer import Signer, VaultSigner | ||
|
||
|
||
class TestVaultSigner(unittest.TestCase): | ||
"""Test VaultSigner""" | ||
|
||
def test_vault_import_sign_verify(self): | ||
# Test full signer flow with vault | ||
# - see tests/scripts/init-vault.sh for how keys are created | ||
# - see tox.ini for how credentials etc. are passed via env vars | ||
keys_and_schemes = [("test-key-ed25519", 1, "ed25519")] | ||
for name, version, scheme in keys_and_schemes: | ||
# Test import | ||
uri, public_key = VaultSigner.import_(name) | ||
|
||
self.assertEqual(uri, f"{VaultSigner.SCHEME}:{name}/{version}") | ||
self.assertEqual(public_key.scheme, scheme) | ||
|
||
# Test load | ||
signer = Signer.from_priv_key_uri(uri, public_key) | ||
self.assertIsInstance(signer, VaultSigner) | ||
|
||
# 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__": | ||
unittest.main(verbosity=1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#!/usr/bin/env bash | ||
|
||
vault server -dev -dev-root-token-id="${VAULT_TOKEN}" & | ||
|
||
until vault status | ||
do | ||
sleep 0.1 | ||
done | ||
|
||
vault secrets enable transit | ||
|
||
vault write -force transit/keys/test-key-ed25519 type=ed25519 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/usr/bin/env bash | ||
|
||
pkill -f "vault server -dev" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters