Skip to content
Draft
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: 0 additions & 1 deletion .packit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ jobs:
- job: copr_build
trigger: pull_request
identifier: copr_pull
manual_trigger: true
targets:
- fedora-all

Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ISORT_MODULES = monkeytype_config.py setup.py bin/stratis src tests

MONKEYTYPE_MODULES = stratis_cli._actions._bind \
stratis_cli._actions._constants \
stratis_cli._actions._crypt \
stratis_cli._actions._data \
stratis_cli._actions._debug \
stratis_cli._actions._environment \
Expand Down
50 changes: 50 additions & 0 deletions docs/stratis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,37 @@ pool unbind <(clevis|keyring)> <pool name> [--token-slot <token slot>]::
mechanism. MOVE NOTICE: The "unbind" subcommand can also be found under
the "pool encryption" subcommand. The "pool unbind" subcommand that you
are using now is deprecated and will be removed in stratis 3.10.0.
pool encryption on --in-place <(--uuid <uuid> |--name <name>)> [--key-desc <key_desc>] [--clevis <(nbde|tang|tpm2)> [--tang-url <tang_url>] [<(--thumbprint <thp> | --trust-url)>]::
Turn encryption on for the specified pool. This operation must encrypt
every block in the pool and takes time proportional to the size of the
pool.
pool encryption off --in-place <(--uuid <uuid> |--name <name>)>::
Turn encryption off for the specified pool. This operation must write
the plain text of every block in the pool and takes time proportional to
the size of the pool.
pool encryption reencrypt --in-place <(--uuid <uuid> |--name <name>)>::
Reencrypt the pool with a new master key. This operation must overwrite
every block in the pool with re-encrypted data and takes time
proportional to the size of the pool.
pool encryption bind <(nbde|tang)> <(--uuid <uuid> |--name <name>)> <(--thumbprint <thp> | --trust-url)> <url>::
Bind the devices in the specified pool to a supplementary encryption
mechanism that uses NBDE (Network-Bound Disc Encryption). *tang* is
an alias for *nbde*.
pool encryption bind tpm2 <(--uuid <uuid> |--name <name>)>::
Bind the devices in the specified pool to a supplementary encryption
mechanism that uses TPM 2.0 (Trusted Platform Module).
pool encryption bind keyring <(--uuid <uuid> |--name <name>)> <keydesc>::
Bind the devices in the specified pool to a supplementary encryption
mechanism using a key in the kernel keyring.
pool encryption rebind clevis <(--uuid <uuid> |--name <name>)> [--token-slot <token slot>]::
Rebind the devices in the specified pool using the Clevis configuration
with which the devices in the pool were previously bound.
pool encryption rebind keyring <(--uuid <uuid> |--name <name>)> <keydesc> [--token-slot <token slot>]::
Rebind the devices in the specified pool using the specified key
description.
pool encryption unbind <(clevis|keyring)> <(--uuid <uuid> |--name <name>)> [--token-slot <token slot>]::
Unbind the devices in the specified pool from the specified encryption
mechanism.
pool set-fs-limit <pool name> <amount> ::
Set the limit on the number of file systems allowed per-pool. This number
may only be increased from its current value.
Expand Down Expand Up @@ -243,6 +274,20 @@ OPTIONS
--token-slot <token slot> ::
For V2 pools only. Use the token slot number to select among
different bindings that use the same encryption method.
--in-place ::
This is a mandatory option that must be set when requesting
a long-running in-place encryption operation. These operations are
a kind that must read and write every block in the pool. Hence these
operations take a time that is linear in the size of the pool.
Additionally, these operations are run in place, that is, the
pool's data blocks are directly modified while it is in use. While
the operation is taking place, automatic administrative actions,
for example, extending filesystems, can not be taken on the pool.
Furthermore, user-initiated actions, such as adding a new device to
a pool are also disabled. The pool administrator should therefore
ensure that no administrative operations will become urgently
necessary while the encryption operation is running. Consider
backing up your data before initiating this operation.


SIZE SPECIFICATION FORMAT FOR INPUT
Expand Down Expand Up @@ -338,6 +383,11 @@ Encryption Enabled: Clevis Configuration::
The Clevis configuration, if the pool is encrypted via Clevis. Only
displayed if metadata version is 1 and encryption is enabled.

Encryption Enabled: Last Time Reencrypted::
The last time the pool was re-encrypted. If the pool has never been
re-encrypted, the value is "Never". Only displayed if metadata
version is 2 and encryption is enabled.

Encryption Enabled: Free Token Slots Remaining::
The number of token slots remaining that can accommodate new
bindings, followed by a list of binding descriptions, ordered by
Expand Down
2 changes: 2 additions & 0 deletions src/stratis_cli/_actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
MANAGER_0_INTERFACE,
POOL_INTERFACE,
)
from ._crypt import CryptActions
from ._debug import (
BlockdevDebugActions,
FilesystemDebugActions,
Expand All @@ -34,3 +35,4 @@
from ._stratis import StratisActions
from ._stratisd_version import check_stratisd_version
from ._top import TopActions
from ._utils import get_errors
179 changes: 179 additions & 0 deletions src/stratis_cli/_actions/_crypt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Copyright 2025 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Miscellaneous whole pool encryption actions.
"""

# isort: STDLIB
import json
from argparse import Namespace

from .._constants import PoolId
from .._errors import (
StratisCliEngineError,
StratisCliIncoherenceError,
StratisCliInPlaceNotSpecified,
StratisCliNoChangeError,
)
from .._stratisd_constants import StratisdErrors
from ._connection import get_object
from ._constants import TOP_OBJECT
from ._utils import long_running_operation


class CryptActions:
"""
Whole pool encryption actions.
"""

@staticmethod
@long_running_operation
def encrypt(namespace: Namespace):
"""
Encrypt a previously unencrypted pool.
"""

if not namespace.in_place:
raise StratisCliInPlaceNotSpecified()

# pylint: disable=import-outside-toplevel
from ._data import MOPool, ObjectManager, Pool, pools

pool_id = PoolId.from_parser_namespace(namespace)
assert pool_id is not None

proxy = get_object(TOP_OBJECT)

managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})

(pool_object_path, mopool) = next(
pools(props=pool_id.managed_objects_key())
.require_unique_match(True)
.search(managed_objects)
)

if bool(MOPool(mopool).Encrypted()):
raise StratisCliNoChangeError("encryption on", pool_id)

(changed, return_code, message) = Pool.Methods.EncryptPool(
get_object(pool_object_path),
{
"key_descs": (
[]
if namespace.key_desc is None
else [((False, 0), namespace.key_desc)]
),
"clevis_infos": (
[]
if namespace.clevis is None
else [
(
(False, 0),
namespace.clevis.pin,
json.dumps(namespace.clevis.config),
)
]
),
},
)

if return_code != StratisdErrors.OK:
raise StratisCliEngineError(return_code, message)

if not changed: # pragma: no cover
raise StratisCliIncoherenceError(
f"Expected to change {pool_id} encryption status to "
"encrypted, but stratisd reports that it did not change "
"the encryption status"
)

@staticmethod
@long_running_operation
def unencrypt(namespace: Namespace):
"""
Unencrypt a previously encrypted pool.
"""
if not namespace.in_place:
raise StratisCliInPlaceNotSpecified()

# pylint: disable=import-outside-toplevel
from ._data import MOPool, ObjectManager, Pool, pools

pool_id = PoolId.from_parser_namespace(namespace)
assert pool_id is not None

proxy = get_object(TOP_OBJECT)

managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})

(pool_object_path, mopool) = next(
pools(props=pool_id.managed_objects_key())
.require_unique_match(True)
.search(managed_objects)
)

if not bool(MOPool(mopool).Encrypted()):
raise StratisCliNoChangeError("encryption off", pool_id)

(changed, return_code, message) = Pool.Methods.DecryptPool(
get_object(pool_object_path), {}
)

if return_code != StratisdErrors.OK: # pragma: no cover
raise StratisCliEngineError(return_code, message)

if not changed: # pragma: no cover
raise StratisCliIncoherenceError(
f"Expected to change {pool_id} encryption status to "
"unencrypted, but stratisd reports that it did not change "
"the pool's encryption status"
)

@staticmethod
@long_running_operation
def reencrypt(namespace: Namespace):
"""
Reencrypt an already encrypted pool with a new key.
"""
if not namespace.in_place:
raise StratisCliInPlaceNotSpecified()

# pylint: disable=import-outside-toplevel
from ._data import ObjectManager, Pool, pools

pool_id = PoolId.from_parser_namespace(namespace)
assert pool_id is not None

proxy = get_object(TOP_OBJECT)

managed_objects = ObjectManager.Methods.GetManagedObjects(proxy, {})

(pool_object_path, _) = next(
pools(props=pool_id.managed_objects_key())
.require_unique_match(True)
.search(managed_objects)
)

(changed, return_code, message) = Pool.Methods.ReencryptPool(
get_object(pool_object_path), {}
)

if return_code != StratisdErrors.OK: # pragma: no cover
raise StratisCliEngineError(return_code, message)

if not changed: # pragma: no cover
raise StratisCliIncoherenceError(
f"Expected to reencrypt {pool_id} with a new key but stratisd "
"reports that it did not perform the operation"
)
2 changes: 1 addition & 1 deletion src/stratis_cli/_actions/_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"deferred until after the stratis_cli module has been fully loaded."
)

DBUS_TIMEOUT_SECONDS = 120
DBUS_TIMEOUT_SECONDS = 60

# Specification for the lowest manager interface supported by the major
# version of stratisd on which this version of the CLI depends.
Expand Down
22 changes: 19 additions & 3 deletions src/stratis_cli/_actions/_introspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,24 @@
<arg name="return_code" type="q" direction="out" />
<arg name="return_string" type="s" direction="out" />
</method>
<method name="DecryptPool">
<arg name="results" type="b" direction="out" />
<arg name="return_code" type="q" direction="out" />
<arg name="return_string" type="s" direction="out" />
</method>
<method name="DestroyFilesystems">
<arg name="filesystems" type="ao" direction="in" />
<arg name="results" type="(bas)" direction="out" />
<arg name="return_code" type="q" direction="out" />
<arg name="return_string" type="s" direction="out" />
</method>
<method name="EncryptPool">
<arg name="key_descs" type="a((bu)s)" direction="in" />
<arg name="clevis_infos" type="a((bu)ss)" direction="in" />
<arg name="results" type="b" direction="out" />
<arg name="return_code" type="q" direction="out" />
<arg name="return_string" type="s" direction="out" />
</method>
<method name="FilesystemMetadata">
<arg name="fs_name" type="(bs)" direction="in" />
<arg name="current" type="b" direction="in" />
Expand Down Expand Up @@ -221,6 +233,11 @@
<arg name="return_code" type="q" direction="out" />
<arg name="return_string" type="s" direction="out" />
</method>
<method name="ReencryptPool">
<arg name="results" type="b" direction="out" />
<arg name="return_code" type="q" direction="out" />
<arg name="return_string" type="s" direction="out" />
</method>
<method name="SetName">
<arg name="name" type="s" direction="in" />
<arg name="result" type="(bs)" direction="out" />
Expand Down Expand Up @@ -249,13 +266,12 @@
<property name="AllocatedSize" type="s" access="read" />
<property name="AvailableActions" type="s" access="read" />
<property name="ClevisInfos" type="v" access="read" />
<property name="Encrypted" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
</property>
<property name="Encrypted" type="b" access="read" />
<property name="FreeTokenSlots" type="(by)" access="read" />
<property name="FsLimit" type="t" access="readwrite" />
<property name="HasCache" type="b" access="read" />
<property name="KeyDescriptions" type="v" access="read" />
<property name="LastReencryptedTimestamp" type="(bs)" access="read" />
<property name="MetadataVersion" type="t" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="const" />
</property>
Expand Down
9 changes: 9 additions & 0 deletions src/stratis_cli/_actions/_list_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from typing import List, Optional, Union

# isort: THIRDPARTY
from dateutil import parser as date_parser
from justbytes import Range

from .._alerts import (
Expand Down Expand Up @@ -360,6 +361,14 @@ def _print_detail_view(
if encrypted:
print("Encryption Enabled: Yes")

(valid, timestamp) = mopool.LastReencryptedTimestamp()
reencrypted = (
date_parser.isoparse(timestamp).astimezone().strftime("%b %d %Y %H:%M")
if valid
else "Never"
)
print(f" Last Time Reencrypted: {reencrypted}")

if metadata_version is MetadataVersion.V1: # pragma: no cover
key_description_str = _non_existent_or_inconsistent_to_str(
EncryptionInfoKeyDescription(mopool.KeyDescriptions())
Expand Down
Loading
Loading