Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ All versions prior to 1.0.0 are untracked.
- Added tests for verifying signatures created with v0.3.1
- cli: `model_signing sign` now supports the `--oauth_force_oob` option (default: False)
- Added support for specifying `--client_id` and `--client_secret` for OIDC authentication.
- cli: Added support for `--allow_symlinks` option

## [1.0.1] - 2024-04-18

Expand Down
9 changes: 8 additions & 1 deletion scripts/tests/test-sign-verify
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ echo "Testing 'sign/verify' certificate when in subdir of model directory and us

rm -f "$(basename "${sigfile}")"
mkdir subdir

# Create a symlink'ed file
echo "foo" > subdir/symlinked
ln -s subdir/symlinked symlink

pushd subdir 1>/dev/null || exit 1

if ! python -m model_signing \
Expand All @@ -173,6 +178,7 @@ if ! python -m model_signing \
--signing_certificate "${DIR}/keys/certificate/signing-key-cert.pem" \
--certificate_chain "${DIR}/keys/certificate/int-ca-cert.pem" \
--ignore-paths "../$(basename "${ignorefile}")" \
--allow_symlinks \
.. ; then
echo "Error: 'sign key' failed"
exit 1
Expand All @@ -185,14 +191,15 @@ if ! python -m model_signing \
--signature "$(basename "${sigfile}")" \
--certificate_chain "${DIR}/keys/certificate/ca-cert.pem" \
--ignore-paths "$(basename "${ignorefile}")" \
--allow_symlinks \
. ; then
echo "Error: 'sign key' failed"
exit 1
fi

# Check which files are part of signature
res=$(get_signed_files "${sigfile}")
exp='["signme-1","signme-2"]'
exp='["signme-1","signme-2","subdir/symlinked","symlink"]'
if [ "${res}" != "${exp}" ]; then
echo "Error: Unexpected files were signed"
echo "Expected: ${exp}"
Expand Down
56 changes: 48 additions & 8 deletions src/model_signing/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@
help="Path to the signing certificate, as a PEM-encoded file.",
)

# Decorator for the commonly used option to allow symlinks
_allow_symlinks_option = click.option(
"--allow_symlinks",
type=bool,
is_flag=True,
help="Whether to allow following symlinks when signing or verifying files.",
)


class _PKICmdGroup(click.Group):
"""A custom group to configure the supported PKI methods."""
Expand Down Expand Up @@ -194,6 +202,7 @@ def _sign() -> None:
@_model_path_argument
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_write_signature_option
@_sigstore_staging_option
@click.option(
Expand Down Expand Up @@ -236,6 +245,7 @@ def _sign_sigstore(
model_path: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
signature: pathlib.Path,
use_ambient_credentials: bool,
use_staging: bool,
Expand Down Expand Up @@ -269,10 +279,12 @@ def _sign_sigstore(
client_id=client_id,
client_secret=client_secret,
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
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)
Expand All @@ -285,6 +297,7 @@ def _sign_sigstore(
@_model_path_argument
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_write_signature_option
@_private_key_option
@click.option(
Expand All @@ -297,6 +310,7 @@ def _sign_private_key(
model_path: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
signature: pathlib.Path,
private_key: pathlib.Path,
password: Optional[str] = None,
Expand All @@ -318,10 +332,12 @@ def _sign_private_key(
model_signing.signing.Config().use_elliptic_key_signer(
private_key=private_key, password=password
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
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)
Expand All @@ -334,12 +350,14 @@ def _sign_private_key(
@_model_path_argument
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_write_signature_option
@_pkcs11_uri_option
def _sign_pkcs11_key(
model_path: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
signature: pathlib.Path,
pkcs11_uri: str,
) -> None:
Expand All @@ -360,10 +378,12 @@ def _sign_pkcs11_key(
model_signing.signing.Config().use_pkcs11_signer(
pkcs11_uri=pkcs11_uri
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
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)
Expand All @@ -376,6 +396,7 @@ def _sign_pkcs11_key(
@_model_path_argument
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_write_signature_option
@_private_key_option
@_signing_certificate_option
Expand All @@ -384,6 +405,7 @@ def _sign_certificate(
model_path: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
signature: pathlib.Path,
private_key: pathlib.Path,
signing_certificate: pathlib.Path,
Expand Down Expand Up @@ -411,10 +433,12 @@ def _sign_certificate(
signing_certificate=signing_certificate,
certificate_chain=certificate_chain,
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
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)
Expand All @@ -427,6 +451,7 @@ def _sign_certificate(
@_model_path_argument
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_write_signature_option
@_pkcs11_uri_option
@_signing_certificate_option
Expand All @@ -435,6 +460,7 @@ def _sign_pkcs11_certificate(
model_path: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
signature: pathlib.Path,
pkcs11_uri: str,
signing_certificate: pathlib.Path,
Expand Down Expand Up @@ -463,10 +489,12 @@ def _sign_pkcs11_certificate(
signing_certificate=signing_certificate,
certificate_chain=certificate_chain,
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
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)
Expand Down Expand Up @@ -497,6 +525,7 @@ def _verify() -> None:
@_read_signature_option
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_sigstore_staging_option
@click.option(
"--identity",
Expand All @@ -517,6 +546,7 @@ def _verify_sigstore(
signature: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
identity: str,
identity_provider: str,
use_staging: bool,
Expand All @@ -537,10 +567,12 @@ def _verify_sigstore(
oidc_issuer=identity_provider,
use_staging=use_staging,
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
ignore_git_paths=ignore_git_paths,
)
.set_allow_symlinks(allow_symlinks)
).verify(model_path, signature)
except Exception as err:
click.echo(f"Verification failed with error: {err}", err=True)
Expand All @@ -554,6 +586,7 @@ def _verify_sigstore(
@_read_signature_option
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@click.option(
"--public_key",
type=pathlib.Path,
Expand All @@ -566,6 +599,7 @@ def _verify_private_key(
signature: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
public_key: pathlib.Path,
) -> None:
"""Verity using a public key (paired with a private one).
Expand All @@ -585,10 +619,12 @@ def _verify_private_key(
model_signing.verifying.Config().use_elliptic_key_verifier(
public_key=public_key
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
ignore_git_paths=ignore_git_paths,
)
.set_allow_symlinks(allow_symlinks)
).verify(model_path, signature)
except Exception as err:
click.echo(f"Verification failed with error: {err}", err=True)
Expand All @@ -602,6 +638,7 @@ def _verify_private_key(
@_read_signature_option
@_ignore_paths_option
@_ignore_git_paths_option
@_allow_symlinks_option
@_certificate_root_of_trust_option
@click.option(
"--log_fingerprints",
Expand All @@ -616,6 +653,7 @@ def _verify_certificate(
signature: pathlib.Path,
ignore_paths: Iterable[pathlib.Path],
ignore_git_paths: bool,
allow_symlinks: bool,
certificate_chain: Iterable[pathlib.Path],
log_fingerprints: bool,
) -> None:
Expand All @@ -640,10 +678,12 @@ def _verify_certificate(
certificate_chain=certificate_chain,
log_fingerprints=log_fingerprints,
).set_hashing_config(
model_signing.hashing.Config().set_ignored_paths(
model_signing.hashing.Config()
.set_ignored_paths(
paths=list(ignore_paths) + [signature],
ignore_git_paths=ignore_git_paths,
)
.set_allow_symlinks(allow_symlinks)
).verify(model_path, signature)
except Exception as err:
click.echo(f"Verification failed with error: {err}", err=True)
Expand Down
4 changes: 4 additions & 0 deletions src/model_signing/_serialization/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
hasher.digest_name, self._allow_symlinks, self._ignore_paths
)

def set_allow_symlinks(self, allow_symlinks: bool) -> None:
"""Set whether following symlinks is allowed."""
self._allow_symlinks = allow_symlinks

@override
def serialize(
self,
Expand Down Expand Up @@ -131,7 +135,7 @@

model_name = model_path.name
if not model_name or model_name == "..":
model_name = os.path.basename(model_path.resolve())

Check warning on line 138 in src/model_signing/_serialization/file.py

View workflow job for this annotation

GitHub Actions / Signing with Python 3.12 on Linux

The following line was not covered in your tests: 138

return manifest.Manifest(
model_name, manifest_items, self._serialization_description
Expand Down
4 changes: 4 additions & 0 deletions src/model_signing/_serialization/file_shard.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@
self._ignore_paths,
)

def set_allow_symlinks(self, allow_symlinks: bool) -> None:
"""Set whether following symlinks is allowed."""
self._allow_symlinks = allow_symlinks

@override
def serialize(
self,
Expand Down Expand Up @@ -166,7 +170,7 @@

model_name = model_path.name
if not model_name or model_name == "..":
model_name = os.path.basename(model_path.resolve())

Check warning on line 173 in src/model_signing/_serialization/file_shard.py

View workflow job for this annotation

GitHub Actions / Signing with Python 3.12 on Linux

The following line was not covered in your tests: 173

return manifest.Manifest(
model_name, manifest_items, self._serialization_description
Expand Down
8 changes: 8 additions & 0 deletions src/model_signing/hashing.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def __init__(self):
self._ignored_paths = frozenset()
self._ignore_git_paths = True
self.use_file_serialization()
self._allow_symlinks = False

def hash(self, model_path: PathLike) -> manifest.Manifest:
"""Hashes a model using the current configuration."""
Expand All @@ -163,6 +164,8 @@ def hash(self, model_path: PathLike) -> manifest.Manifest:
]
)

self._serializer.set_allow_symlinks(self._allow_symlinks)

return self._serializer.serialize(
pathlib.Path(model_path), ignore_paths=ignored_paths
)
Expand Down Expand Up @@ -368,3 +371,8 @@ def add_ignored_paths(
newset = set(self._ignored_paths)
newset.update([os.path.join(model_path, p) for p in paths])
self._ignored_paths = newset

def set_allow_symlinks(self, allow_symlinks: bool) -> Self:
"""Set whether following symlinks is allowed."""
self._allow_symlinks = allow_symlinks
return self
Loading