Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ All versions prior to 1.0.0 are untracked.
## [Unreleased]

### Added
- ...
- Added support for signing with Key Management Service (KMS) providers through KMS URIs. Supported providers include AWS KMS, Google Cloud KMS, Azure Key Vault, and a file-based backend for testing. Install with `pip install model-signing[kms]` to enable this functionality.

### Changed
- ...
Expand Down
2 changes: 1 addition & 1 deletion Containerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ COPY src /app/src
COPY pyproject.toml /app/
COPY README.md /app/
COPY LICENSE /app/
RUN pip install .[pkcs11,otel]
RUN pip install .[pkcs11,otel,kms]

FROM base AS minimal_image
COPY --from=minimal_install /usr/local/bin /usr/local/bin
Expand Down
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ We support generating signatures via [Sigstore](https://www.sigstore.dev/), a
tool for making code signatures transparent without requiring management of
cryptographic key material. But we also support traditional signing methods, so
models can be signed with public keys or signing certificates as well as
PKCS #11 enabled devices *(install with `pip install model-signing[pkcs11]` to enable this functionality)*.
PKCS #11 enabled devices *(install with `pip install model-signing[pkcs11]` to enable this functionality)*
and Key Management Service (KMS) providers *(install with `pip install model-signing[kms]` to enable this functionality)*.

The signing part creates a
[sigstore bundle](https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto)
Expand Down Expand Up @@ -246,6 +247,61 @@ the PKCS #11 device and store it in a file in PEM format. With can then use:
--public_key key.pub /path/to/your/model
```

#### Signing with KMS URIs

Signing with Key Management Service (KMS) providers is supported through
KMS URIs. The URI specifies the KMS provider and key location. Supported
providers include AWS KMS, Google Cloud KMS, Azure Key Vault, and a file-based
backend for testing.

The following KMS providers are supported:

- AWS KMS: `kms://aws/<key-id-or-arn>?region=<region>`
- Requires `boto3` library
- The key-id-or-arn can be either:
- A simple key ID (e.g., `f26f2baa-8865-459d-a275-8fca1d15119f`)
- A full key ARN (e.g., `arn:aws:kms:us-east-1:123456789012:key/f26f2baa-8865-459d-a275-8fca1d15119f`)
- Region is optional and defaults to the configured AWS region
- Google Cloud KMS: `kms://gcp/<project>/<location>/<keyring>/<key>`
- Requires `google-cloud-kms` library
- The key must be configured for asymmetric signing with ECDSA
- Azure Key Vault: `kms://azure/<vault-url>/<key-name>?version=<version>`
- Requires `azure-keyvault-keys` and `azure-identity` libraries
- The vault-url should be the full vault URL (e.g., `https://vault.vault.azure.net`)
- Version is optional and defaults to the latest version
- File (for testing): `kms://file/<path>`
- Uses a local PEM-encoded private key file
- Useful for testing without cloud KMS access

The signing key must be of type NIST P256/384/521 (secp256r1/secp384r1/secp521r1).

With a KMS URI, we can use the following for signing:

```bash
[...]$ model_signing sign kms-key --signature model.sig \
--kms_uri "kms://aws/arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012" \
/path/to/your/model
```

For signature verification, retrieve the public key from the KMS and store it
in a file in PEM format. Then use:

```bash
[...]$ model_signing verify key --signature model.sig \
--public_key key.pub /path/to/your/model
```

To install KMS support, use:

```bash
[...]$ pip install model-signing[kms]
```

Or install provider-specific packages:
- AWS: `pip install boto3`
- Google Cloud: `pip install google-cloud-kms`
- Azure: `pip install azure-keyvault-keys azure-identity`

#### OpenTelemetry Support

Model signing supports optional distributed tracing and observability through OpenTelemetry. This allows you to monitor signing operations, track performance, and integrate with observability platforms.
Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ keywords = [
pkcs11 = [
"PyKCS11",
]
kms = [
"boto3",
"google-cloud-kms",
"azure-keyvault-keys",
"azure-identity",
]
otel = [
"opentelemetry-api",
"opentelemetry-sdk",
Expand Down Expand Up @@ -85,6 +91,7 @@ randomize = true
extra-args = ["-m", "not integration"]
features = [
"pkcs11",
"kms",
]

[[tool.hatch.envs.hatch-test.matrix]]
Expand Down Expand Up @@ -135,6 +142,7 @@ extra-dependencies = [
]
features = [
"pkcs11",
"kms",
]
installer = "pip"
python = "3.12"
Expand Down
67 changes: 67 additions & 0 deletions src/model_signing/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class _PKICmdGroup(click.Group):
"certificate",
"pkcs11-key",
"pkcs11-certificate",
"kms-key",
]

def get_command(
Expand Down Expand Up @@ -498,6 +499,72 @@ def _sign_pkcs11_key(
click.echo("Signing succeeded")


# Decorator for the commonly used option to set a KMS URI
_kms_uri_option = click.option(
"--kms_uri",
type=str,
metavar="KMS_URI",
required=True,
help=(
"KMS URI of the private key (e.g., kms://aws/key-id, "
"kms://aws/arn:aws:kms:region:account-id:key/key-id, "
"kms://gcp/project/location/keyring/key, kms://azure/vault/key)."
),
)


@_sign.command(name="kms-key")
@_model_path_argument
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_write_signature_option
@_kms_uri_option
def _sign_kms_key(
model_path: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
signature: pathlib.Path,
kms_uri: str,
) -> None:
"""Sign using a private key using a KMS URI.

Signing the model at MODEL_PATH, produces the signature at SIGNATURE_PATH
(as per `--signature` option). Files in IGNORE_PATHS are not part of the
signature.

Signing can be achieved by using a key stored in a Key Management Service.
Pass the KMS URI of the signing key using `--kms_uri`.

Supported KMS providers:
- AWS KMS: kms://aws/<key-id-or-arn>?region=<region>
- Google Cloud KMS: kms://gcp/<project>/<location>/<keyring>/<key>
- Azure Key Vault: kms://azure/<vault-url>/<key-name>?version=<version>
- File (for testing): kms://file/<path>

Note that this method does not provide a way to tie to the identity of the
signer, outside of pairing the keys. Also note that we don't offer key
management protocols.
"""
try:
ignored = _resolve_ignore_paths(
model_path, list(ignore_paths) + [signature]
)
model_signing.signing.Config().use_kms_signer(
kms_uri=kms_uri
).set_hashing_config(
model_signing.hashing.Config()
.set_ignored_paths(paths=ignored, ignore_git_paths=ignore_git_paths)
.set_allow_symlinks(allow_symlinks)
).sign(model_path, signature)
except Exception as err:
click.echo(f"Signing failed with error: {err}", err=True)
sys.exit(1)

click.echo("Signing succeeded")


@_sign.command(name="certificate")
@_model_path_argument
@_ignore_paths_option
Expand Down
Loading
Loading