Skip to content

Commit

Permalink
Add alternative admin cli (#523)
Browse files Browse the repository at this point in the history
* Add non-released tuf dependency (WIP)

Install python-tuf from non-released revision to get improved
VerificationResult. Revert when released!!

NOTE:

* Updated pyproject.toml manually (for pip install -e .)
* Updated Pipfile via:
  ```
  pipenv install git+https://github.com/theupdateframework/python-tuf@be55b87
  ```
  This also updated Pipfile.lock including all sorts of unrelated updates.

* Updated requirements* files with `make requirements`

See related #507

Signed-off-by: Lukas Puehringer <[email protected]>

* Add alternative admin cli

Added commands:

- `rstuf admin2 ceremony`
- `rstuf admin2 update`
- `rstuf admin2 sign`

Previous work, related discussion, and detailed reasons for the re-write
can be found in #477 and #490. These PRs are superseded by this PR.

Currently, the cli does not interact with the RSTUF API, but reads input
data from files passed as cli arguments (update, sign) and optionally
writes output data to file using the `--payload-out` option (ceremony,
update, sign).

Preliminary API integration can be found in #477. I suggest to
re-implement this in a separate PR, as well as any presentation
improvements discussed in #477.

Signed-off-by: Lukas Puehringer <[email protected]>

* Add test files for alternative cli

* tests/files/pem:
  3 test key pairs in standard pem/pkcs8/subjectPublicKeyInfo format
  copied from secure-systems-lab/securesystemslib@7952c3f
  (password is 'hunter2')

* tests/files/root:
  exemplary root metadata, crafted manually

* tests/files/payload:
  exemplary payload data, crafted using the new cli

Signed-off-by: Lukas Puehringer <[email protected]>

* Add tests for alternative admin cli

Signed-off-by: Lukas Puehringer <[email protected]>

* Minor refactor of _filter_root_verification_results

Try simplify the filter logic.

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2: rename -o, --payload-out to -s, --save

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2: give user feedback about saving file

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2 ceremony: remove targets base url

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2 update: change default in threshold dialog

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2: add key name prompt to online key dialog

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2: warn if `-s` not provided

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2 sign: raise if prev root is missing

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2 sign: raise if already signed and add test

calling the sign cli on fully signed metadata is a usage mistake.
Raising instead of just exiting seems semantically correct (and makes
testing easier)

Signed-off-by: Lukas Puehringer <[email protected]>

* admin2 ceremony: update api format

Switch to new api format for ceremony cli payload result.

Includes a minor dialog restructure and related helper refactor:

The sections "Metadata expiration" and "Artifacts" in the dialog are
replaced with an "Online role settings" section, which prompts for all
online role expiries and bins numbers. The root expiry prompt is moved
to a separate "Root expiry" section, even though it is still included
with the online role settings payload.

Signed-off-by: Lukas Puehringer <[email protected]>

---------

Signed-off-by: Lukas Puehringer <[email protected]>
Co-authored-by: Martin Vrachev <[email protected]>
  • Loading branch information
lukpueh and MVrachev authored Mar 26, 2024
1 parent 2b50486 commit 5d0bffc
Show file tree
Hide file tree
Showing 22 changed files with 1,774 additions and 78 deletions.
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ rich = "*"
auto-click-auto = "*"
PyNaCl = "==1.5.0"
requests = "*"
tuf = "==3.1.0"
dynaconf = {extras = ["yaml"], version = "*"}
isort = "*"
sqlalchemy = "*"
psycopg2 = "*"
tuf = {ref = "be55b87", git = "git+https://github.com/theupdateframework/python-tuf"}

[dev-packages]
black = "*"
Expand Down
74 changes: 3 additions & 71 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ dependencies = [
"rich",
"rich-click",
"securesystemslib[crypto]==0.31.0",
"tuf==3.1.0",
"tuf @ git+https://github.com/theupdateframework/python-tuf@be55b87"
]
dynamic = ["version"]

[tool.mypy]
exclude = "docs/"

[[tool.mypy.overrides]]
module = ["dynaconf", "pretend"]
module = ["dynaconf", "pretend", "securesystemslib.*",]
ignore_missing_imports = true

[tool.hatch.version]
path = "repository_service_tuf/__version__.py"

[tool.hatch.metadata]
allow-direct-references = true

[project.optional-dependencies]
psycopg2 = ["psycopg2>=2.9.5"] # required by import-artifacts sub-command
sqlalchemy = ["sqlalchemy>=2.0.1"] # required by import-artifacts sub-command
Expand Down
16 changes: 16 additions & 0 deletions repository_service_tuf/cli/admin2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2022-2023 VMware Inc
#
# SPDX-License-Identifier: MIT

"""Alternative admin cli
Provides alternative ceremony, metadata update, and sign admin cli commands.
"""

from repository_service_tuf.cli import rstuf


@rstuf.group() # type: ignore
def admin2():
"""Alternative admin interface"""
101 changes: 101 additions & 0 deletions repository_service_tuf/cli/admin2/ceremony.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import json
from dataclasses import asdict

import click
from rich.markdown import Markdown
from tuf.api.metadata import Metadata, Root

# TODO: Should we use the global rstuf console exclusively? We do use it for
# `console.print`, but not with `Confirm/Prompt.ask`. The latter uses a default
# console from `rich`. Using a single console everywhere would makes custom
# configuration or, more importantly, patching in tests easier:
# https://rich.readthedocs.io/en/stable/console.html#console-api
# https://rich.readthedocs.io/en/stable/console.html#capturing-output
from repository_service_tuf.cli import console
from repository_service_tuf.cli.admin2 import admin2
from repository_service_tuf.cli.admin2.helpers import (
BinsRole,
CeremonyPayload,
Metadatas,
Role,
Roles,
Settings,
_add_root_signatures_prompt,
_configure_online_key_prompt,
_configure_root_keys_prompt,
_expiry_prompt,
_online_settings_prompt,
_print_root,
_root_threshold_prompt,
_warn_no_save,
)


@admin2.command() # type: ignore
@click.option(
"--save",
"-s",
is_flag=False,
flag_value="ceremony-payload.json",
help="Write json result to FILENAME (default: 'ceremony-payload.json')",
type=click.File("w"),
)
def ceremony(save) -> None:
"""Bootstrap Ceremony to create initial root metadata and RSTUF config."""
console.print("\n", Markdown("# Metadata Bootstrap Tool"))

if not save:
_warn_no_save()

root = Root()

###########################################################################
# Configure online role settings
console.print(Markdown("## Online role settings"))
online = _online_settings_prompt()

console.print(Markdown("## Root expiry"))
root_days, root_date = _expiry_prompt("root")
root.expires = root_date

roles = Roles(
Role(root_days),
Role(online.timestamp_expiry),
Role(online.snapshot_expiry),
Role(online.targets_expiry),
BinsRole(online.bins_expiry, online.bins_number),
)

###########################################################################
# Configure Root Keys
console.print(Markdown("## Root Keys"))
root_role = root.get_delegated_role(Root.type)
root_role.threshold = _root_threshold_prompt()
_configure_root_keys_prompt(root)

###########################################################################
# Configure Online Key
console.print(Markdown("## Online Key"))
_configure_online_key_prompt(root)

###########################################################################
# Review Metadata
console.print(Markdown("## Review"))
_print_root(root)
# TODO: ask to continue? or abort? or start over?

###########################################################################
# Sign Metadata
console.print(Markdown("## Sign"))
root_md = Metadata(root)
_add_root_signatures_prompt(root_md, None)

###########################################################################
# Dump payload
# TODO: post to API
if save:
metadatas = Metadatas(root_md.to_dict())
settings = Settings(roles)
payload = CeremonyPayload(settings, metadatas)
json.dump(asdict(payload), save, indent=2)
console.print(f"Saved result to '{save.name}'")
Loading

0 comments on commit 5d0bffc

Please sign in to comment.