flwr 1.22.0__py3-none-any.whl → 1.24.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flwr/__init__.py +16 -5
- flwr/app/error.py +2 -2
- flwr/app/exception.py +3 -3
- flwr/cli/app.py +34 -1
- flwr/cli/app_cmd/__init__.py +23 -0
- flwr/cli/app_cmd/publish.py +285 -0
- flwr/cli/app_cmd/review.py +252 -0
- flwr/cli/auth_plugin/__init__.py +15 -6
- flwr/cli/auth_plugin/auth_plugin.py +94 -0
- flwr/cli/auth_plugin/noop_auth_plugin.py +101 -0
- flwr/cli/auth_plugin/oidc_cli_plugin.py +46 -32
- flwr/cli/build.py +166 -53
- flwr/cli/{cli_user_auth_interceptor.py → cli_account_auth_interceptor.py} +29 -11
- flwr/cli/config_utils.py +101 -13
- flwr/cli/federation/__init__.py +24 -0
- flwr/cli/federation/ls.py +140 -0
- flwr/cli/federation/show.py +317 -0
- flwr/cli/install.py +91 -13
- flwr/cli/log.py +54 -11
- flwr/cli/login/login.py +41 -27
- flwr/cli/ls.py +177 -133
- flwr/cli/new/new.py +175 -40
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +1 -0
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +3 -3
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +1 -1
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +2 -2
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +1 -1
- flwr/cli/pull.py +12 -7
- flwr/cli/run/run.py +82 -31
- flwr/cli/run_utils.py +130 -0
- flwr/cli/stop.py +27 -9
- flwr/cli/supernode/__init__.py +25 -0
- flwr/cli/supernode/ls.py +268 -0
- flwr/cli/supernode/register.py +190 -0
- flwr/cli/supernode/unregister.py +140 -0
- flwr/cli/utils.py +464 -81
- flwr/client/__init__.py +2 -1
- flwr/client/dpfedavg_numpy_client.py +4 -1
- flwr/client/grpc_adapter_client/connection.py +12 -15
- flwr/client/grpc_rere_client/connection.py +68 -41
- flwr/client/grpc_rere_client/grpc_adapter.py +34 -14
- flwr/client/grpc_rere_client/{client_interceptor.py → node_auth_client_interceptor.py} +5 -7
- flwr/client/message_handler/message_handler.py +2 -2
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +10 -8
- flwr/client/numpy_client.py +1 -1
- flwr/client/rest_client/connection.py +94 -51
- flwr/client/run_info_store.py +4 -5
- flwr/client/typing.py +1 -1
- flwr/clientapp/__init__.py +1 -2
- flwr/{client → clientapp}/client_app.py +9 -10
- flwr/clientapp/mod/centraldp_mods.py +16 -17
- flwr/clientapp/mod/localdp_mod.py +8 -9
- flwr/clientapp/typing.py +1 -1
- flwr/{client/clientapp → clientapp}/utils.py +4 -4
- flwr/common/address.py +1 -2
- flwr/common/args.py +3 -4
- flwr/common/config.py +13 -16
- flwr/common/constant.py +56 -13
- flwr/common/differential_privacy.py +3 -4
- flwr/common/event_log_plugin/event_log_plugin.py +3 -4
- flwr/common/exit/exit.py +15 -2
- flwr/common/exit/exit_code.py +39 -10
- flwr/common/exit/exit_handler.py +6 -2
- flwr/common/exit/signal_handler.py +5 -5
- flwr/common/grpc.py +6 -6
- flwr/common/inflatable_protobuf_utils.py +1 -1
- flwr/common/inflatable_utils.py +48 -31
- flwr/common/logger.py +19 -19
- flwr/common/message.py +4 -4
- flwr/common/object_ref.py +7 -7
- flwr/common/record/array.py +6 -6
- flwr/common/record/arrayrecord.py +18 -21
- flwr/common/record/configrecord.py +3 -3
- flwr/common/record/recorddict.py +5 -5
- flwr/common/record/typeddict.py +9 -2
- flwr/common/recorddict_compat.py +7 -10
- flwr/common/retry_invoker.py +20 -20
- flwr/common/secure_aggregation/crypto/symmetric_encryption.py +1 -89
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +3 -3
- flwr/common/serde.py +9 -6
- flwr/common/serde_utils.py +2 -2
- flwr/common/telemetry.py +9 -5
- flwr/common/typing.py +59 -43
- flwr/compat/client/app.py +39 -38
- flwr/compat/client/grpc_client/connection.py +13 -13
- flwr/compat/server/app.py +5 -6
- flwr/proto/appio_pb2.py +13 -3
- flwr/proto/appio_pb2.pyi +134 -65
- flwr/proto/appio_pb2_grpc.py +20 -0
- flwr/proto/appio_pb2_grpc.pyi +27 -0
- flwr/proto/clientappio_pb2.py +17 -7
- flwr/proto/clientappio_pb2.pyi +15 -0
- flwr/proto/clientappio_pb2_grpc.py +206 -40
- flwr/proto/clientappio_pb2_grpc.pyi +168 -53
- flwr/proto/control_pb2.py +72 -40
- flwr/proto/control_pb2.pyi +319 -87
- flwr/proto/control_pb2_grpc.py +339 -28
- flwr/proto/control_pb2_grpc.pyi +209 -37
- flwr/proto/error_pb2.py +13 -3
- flwr/proto/error_pb2.pyi +24 -6
- flwr/proto/error_pb2_grpc.py +20 -0
- flwr/proto/error_pb2_grpc.pyi +27 -0
- flwr/proto/fab_pb2.py +24 -10
- flwr/proto/fab_pb2.pyi +68 -20
- flwr/proto/fab_pb2_grpc.py +20 -0
- flwr/proto/fab_pb2_grpc.pyi +27 -0
- flwr/proto/federation_pb2.py +38 -0
- flwr/proto/federation_pb2.pyi +56 -0
- flwr/proto/federation_pb2_grpc.py +24 -0
- flwr/proto/federation_pb2_grpc.pyi +31 -0
- flwr/proto/fleet_pb2.py +45 -27
- flwr/proto/fleet_pb2.pyi +186 -70
- flwr/proto/fleet_pb2_grpc.py +277 -66
- flwr/proto/fleet_pb2_grpc.pyi +201 -55
- flwr/proto/grpcadapter_pb2.py +14 -4
- flwr/proto/grpcadapter_pb2.pyi +38 -16
- flwr/proto/grpcadapter_pb2_grpc.py +35 -4
- flwr/proto/grpcadapter_pb2_grpc.pyi +38 -7
- flwr/proto/heartbeat_pb2.py +17 -7
- flwr/proto/heartbeat_pb2.pyi +51 -22
- flwr/proto/heartbeat_pb2_grpc.py +20 -0
- flwr/proto/heartbeat_pb2_grpc.pyi +27 -0
- flwr/proto/log_pb2.py +13 -3
- flwr/proto/log_pb2.pyi +34 -11
- flwr/proto/log_pb2_grpc.py +20 -0
- flwr/proto/log_pb2_grpc.pyi +27 -0
- flwr/proto/message_pb2.py +15 -5
- flwr/proto/message_pb2.pyi +154 -86
- flwr/proto/message_pb2_grpc.py +20 -0
- flwr/proto/message_pb2_grpc.pyi +27 -0
- flwr/proto/node_pb2.py +16 -4
- flwr/proto/node_pb2.pyi +77 -4
- flwr/proto/node_pb2_grpc.py +20 -0
- flwr/proto/node_pb2_grpc.pyi +27 -0
- flwr/proto/recorddict_pb2.py +13 -3
- flwr/proto/recorddict_pb2.pyi +184 -107
- flwr/proto/recorddict_pb2_grpc.py +20 -0
- flwr/proto/recorddict_pb2_grpc.pyi +27 -0
- flwr/proto/run_pb2.py +40 -31
- flwr/proto/run_pb2.pyi +149 -84
- flwr/proto/run_pb2_grpc.py +20 -0
- flwr/proto/run_pb2_grpc.pyi +27 -0
- flwr/proto/serverappio_pb2.py +13 -3
- flwr/proto/serverappio_pb2.pyi +32 -8
- flwr/proto/serverappio_pb2_grpc.py +246 -65
- flwr/proto/serverappio_pb2_grpc.pyi +221 -85
- flwr/proto/simulationio_pb2.py +16 -8
- flwr/proto/simulationio_pb2.pyi +15 -0
- flwr/proto/simulationio_pb2_grpc.py +162 -41
- flwr/proto/simulationio_pb2_grpc.pyi +149 -55
- flwr/proto/transport_pb2.py +20 -10
- flwr/proto/transport_pb2.pyi +249 -160
- flwr/proto/transport_pb2_grpc.py +35 -4
- flwr/proto/transport_pb2_grpc.pyi +38 -8
- flwr/server/app.py +173 -127
- flwr/server/client_manager.py +4 -5
- flwr/server/client_proxy.py +10 -11
- flwr/server/compat/app.py +4 -5
- flwr/server/compat/app_utils.py +2 -1
- flwr/server/compat/grid_client_proxy.py +10 -12
- flwr/server/compat/legacy_context.py +3 -4
- flwr/server/fleet_event_log_interceptor.py +2 -1
- flwr/server/grid/grid.py +2 -3
- flwr/server/grid/grpc_grid.py +10 -8
- flwr/server/grid/inmemory_grid.py +4 -4
- flwr/server/run_serverapp.py +2 -3
- flwr/server/server.py +34 -39
- flwr/server/server_app.py +7 -8
- flwr/server/server_config.py +1 -2
- flwr/server/serverapp/app.py +34 -28
- flwr/server/serverapp_components.py +4 -5
- flwr/server/strategy/aggregate.py +9 -8
- flwr/server/strategy/bulyan.py +13 -11
- flwr/server/strategy/dp_adaptive_clipping.py +16 -20
- flwr/server/strategy/dp_fixed_clipping.py +12 -17
- flwr/server/strategy/dpfedavg_adaptive.py +3 -4
- flwr/server/strategy/dpfedavg_fixed.py +6 -10
- flwr/server/strategy/fault_tolerant_fedavg.py +14 -13
- flwr/server/strategy/fedadagrad.py +18 -14
- flwr/server/strategy/fedadam.py +16 -14
- flwr/server/strategy/fedavg.py +16 -17
- flwr/server/strategy/fedavg_android.py +15 -15
- flwr/server/strategy/fedavgm.py +21 -18
- flwr/server/strategy/fedmedian.py +2 -3
- flwr/server/strategy/fedopt.py +11 -10
- flwr/server/strategy/fedprox.py +10 -9
- flwr/server/strategy/fedtrimmedavg.py +12 -11
- flwr/server/strategy/fedxgb_bagging.py +13 -11
- flwr/server/strategy/fedxgb_cyclic.py +6 -6
- flwr/server/strategy/fedxgb_nn_avg.py +4 -4
- flwr/server/strategy/fedyogi.py +16 -14
- flwr/server/strategy/krum.py +12 -11
- flwr/server/strategy/qfedavg.py +16 -15
- flwr/server/strategy/strategy.py +6 -9
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +19 -8
- flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +1 -2
- flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +3 -4
- flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +10 -12
- flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +1 -3
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +136 -42
- flwr/server/superlink/fleet/grpc_rere/{server_interceptor.py → node_auth_server_interceptor.py} +28 -51
- flwr/server/superlink/fleet/message_handler/message_handler.py +100 -49
- flwr/server/superlink/fleet/rest_rere/rest_api.py +54 -33
- flwr/server/superlink/fleet/vce/backend/backend.py +2 -2
- flwr/server/superlink/fleet/vce/backend/raybackend.py +6 -6
- flwr/server/superlink/fleet/vce/vce_api.py +32 -13
- flwr/server/superlink/linkstate/in_memory_linkstate.py +266 -207
- flwr/server/superlink/linkstate/linkstate.py +161 -62
- flwr/server/superlink/linkstate/linkstate_factory.py +24 -6
- flwr/server/superlink/linkstate/sqlite_linkstate.py +698 -638
- flwr/server/superlink/linkstate/utils.py +9 -60
- flwr/server/superlink/serverappio/serverappio_grpc.py +1 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +28 -23
- flwr/server/superlink/simulation/simulationio_grpc.py +1 -2
- flwr/server/superlink/simulation/simulationio_servicer.py +19 -14
- flwr/server/superlink/utils.py +4 -6
- flwr/server/typing.py +1 -1
- flwr/server/utils/tensorboard.py +15 -8
- flwr/server/utils/validator.py +2 -3
- flwr/server/workflow/default_workflows.py +5 -5
- flwr/server/workflow/secure_aggregation/secagg_workflow.py +2 -4
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +12 -10
- flwr/serverapp/strategy/bulyan.py +16 -15
- flwr/serverapp/strategy/dp_adaptive_clipping.py +12 -11
- flwr/serverapp/strategy/dp_fixed_clipping.py +11 -14
- flwr/serverapp/strategy/fedadagrad.py +10 -11
- flwr/serverapp/strategy/fedadam.py +10 -11
- flwr/serverapp/strategy/fedavg.py +9 -10
- flwr/serverapp/strategy/fedavgm.py +17 -16
- flwr/serverapp/strategy/fedmedian.py +2 -2
- flwr/serverapp/strategy/fedopt.py +10 -11
- flwr/serverapp/strategy/fedprox.py +7 -8
- flwr/serverapp/strategy/fedtrimmedavg.py +9 -9
- flwr/serverapp/strategy/fedxgb_bagging.py +3 -3
- flwr/serverapp/strategy/fedxgb_cyclic.py +9 -9
- flwr/serverapp/strategy/fedyogi.py +9 -11
- flwr/serverapp/strategy/krum.py +7 -7
- flwr/serverapp/strategy/multikrum.py +9 -9
- flwr/serverapp/strategy/qfedavg.py +17 -16
- flwr/serverapp/strategy/strategy.py +6 -9
- flwr/serverapp/strategy/strategy_utils.py +7 -8
- flwr/simulation/app.py +46 -42
- flwr/simulation/legacy_app.py +12 -12
- flwr/simulation/ray_transport/ray_actor.py +11 -12
- flwr/simulation/ray_transport/ray_client_proxy.py +12 -13
- flwr/simulation/run_simulation.py +44 -43
- flwr/simulation/simulationio_connection.py +4 -4
- flwr/supercore/cli/flower_superexec.py +3 -4
- flwr/supercore/constant.py +52 -0
- flwr/supercore/corestate/corestate.py +24 -3
- flwr/supercore/corestate/in_memory_corestate.py +138 -0
- flwr/supercore/corestate/sqlite_corestate.py +157 -0
- flwr/supercore/ffs/disk_ffs.py +1 -2
- flwr/supercore/ffs/ffs.py +1 -2
- flwr/supercore/ffs/ffs_factory.py +1 -2
- flwr/{common → supercore}/heartbeat.py +20 -25
- flwr/supercore/object_store/in_memory_object_store.py +1 -6
- flwr/supercore/object_store/object_store.py +1 -2
- flwr/supercore/object_store/object_store_factory.py +27 -8
- flwr/supercore/object_store/sqlite_object_store.py +253 -0
- flwr/{client/clientapp → supercore/primitives}/__init__.py +1 -1
- flwr/supercore/primitives/asymmetric.py +117 -0
- flwr/supercore/primitives/asymmetric_ed25519.py +175 -0
- flwr/supercore/sqlite_mixin.py +159 -0
- flwr/supercore/superexec/plugin/base_exec_plugin.py +1 -2
- flwr/supercore/superexec/plugin/exec_plugin.py +3 -3
- flwr/supercore/superexec/run_superexec.py +9 -13
- flwr/supercore/utils.py +20 -0
- flwr/superlink/artifact_provider/artifact_provider.py +1 -2
- flwr/{common → superlink}/auth_plugin/__init__.py +6 -6
- flwr/superlink/auth_plugin/auth_plugin.py +88 -0
- flwr/superlink/auth_plugin/noop_auth_plugin.py +84 -0
- flwr/superlink/federation/__init__.py +24 -0
- flwr/superlink/federation/federation_manager.py +64 -0
- flwr/superlink/federation/noop_federation_manager.py +71 -0
- flwr/superlink/servicer/control/{control_user_auth_interceptor.py → control_account_auth_interceptor.py} +41 -32
- flwr/superlink/servicer/control/control_event_log_interceptor.py +7 -7
- flwr/superlink/servicer/control/control_grpc.py +18 -17
- flwr/superlink/servicer/control/control_license_interceptor.py +3 -3
- flwr/superlink/servicer/control/control_servicer.py +239 -63
- flwr/supernode/cli/flower_supernode.py +74 -26
- flwr/supernode/nodestate/in_memory_nodestate.py +60 -49
- flwr/supernode/nodestate/nodestate.py +7 -8
- flwr/supernode/nodestate/nodestate_factory.py +7 -4
- flwr/supernode/runtime/run_clientapp.py +43 -24
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +40 -10
- flwr/supernode/start_client_internal.py +175 -51
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/METADATA +8 -8
- flwr-1.24.0.dist-info/RECORD +454 -0
- flwr/common/auth_plugin/auth_plugin.py +0 -149
- flwr/supercore/object_store/utils.py +0 -43
- flwr-1.22.0.dist-info/RECORD +0 -428
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/WHEEL +0 -0
- {flwr-1.22.0.dist-info → flwr-1.24.0.dist-info}/entry_points.txt +0 -0
|
@@ -17,20 +17,23 @@
|
|
|
17
17
|
|
|
18
18
|
import hashlib
|
|
19
19
|
import time
|
|
20
|
-
from collections.abc import Generator
|
|
20
|
+
from collections.abc import Generator, Sequence
|
|
21
21
|
from logging import ERROR, INFO
|
|
22
|
-
from typing import Any,
|
|
22
|
+
from typing import Any, cast
|
|
23
23
|
|
|
24
24
|
import grpc
|
|
25
25
|
|
|
26
26
|
from flwr.cli.config_utils import get_fab_metadata
|
|
27
27
|
from flwr.common import Context, RecordDict, now
|
|
28
|
-
from flwr.common.auth_plugin import ControlAuthPlugin
|
|
29
28
|
from flwr.common.constant import (
|
|
30
29
|
FAB_MAX_SIZE,
|
|
30
|
+
HEARTBEAT_DEFAULT_INTERVAL,
|
|
31
31
|
LOG_STREAM_INTERVAL,
|
|
32
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
32
33
|
NO_ARTIFACT_PROVIDER_MESSAGE,
|
|
33
|
-
|
|
34
|
+
NODE_NOT_FOUND_MESSAGE,
|
|
35
|
+
PUBLIC_KEY_ALREADY_IN_USE_MESSAGE,
|
|
36
|
+
PUBLIC_KEY_NOT_VALID,
|
|
34
37
|
PULL_UNFINISHED_RUN_MESSAGE,
|
|
35
38
|
RUN_ID_NOT_FOUND_MESSAGE,
|
|
36
39
|
Status,
|
|
@@ -49,23 +52,37 @@ from flwr.proto.control_pb2 import ( # pylint: disable=E0611
|
|
|
49
52
|
GetAuthTokensResponse,
|
|
50
53
|
GetLoginDetailsRequest,
|
|
51
54
|
GetLoginDetailsResponse,
|
|
55
|
+
ListFederationsRequest,
|
|
56
|
+
ListFederationsResponse,
|
|
57
|
+
ListNodesRequest,
|
|
58
|
+
ListNodesResponse,
|
|
52
59
|
ListRunsRequest,
|
|
53
60
|
ListRunsResponse,
|
|
54
61
|
PullArtifactsRequest,
|
|
55
62
|
PullArtifactsResponse,
|
|
63
|
+
RegisterNodeRequest,
|
|
64
|
+
RegisterNodeResponse,
|
|
65
|
+
ShowFederationRequest,
|
|
66
|
+
ShowFederationResponse,
|
|
56
67
|
StartRunRequest,
|
|
57
68
|
StartRunResponse,
|
|
58
69
|
StopRunRequest,
|
|
59
70
|
StopRunResponse,
|
|
60
71
|
StreamLogsRequest,
|
|
61
72
|
StreamLogsResponse,
|
|
73
|
+
UnregisterNodeRequest,
|
|
74
|
+
UnregisterNodeResponse,
|
|
62
75
|
)
|
|
76
|
+
from flwr.proto.federation_pb2 import Federation # pylint: disable=E0611
|
|
77
|
+
from flwr.proto.node_pb2 import NodeInfo # pylint: disable=E0611
|
|
63
78
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
64
79
|
from flwr.supercore.ffs import FfsFactory
|
|
65
80
|
from flwr.supercore.object_store import ObjectStore, ObjectStoreFactory
|
|
81
|
+
from flwr.supercore.primitives.asymmetric import bytes_to_public_key, uses_nist_ec_curve
|
|
66
82
|
from flwr.superlink.artifact_provider import ArtifactProvider
|
|
83
|
+
from flwr.superlink.auth_plugin import ControlAuthnPlugin
|
|
67
84
|
|
|
68
|
-
from .
|
|
85
|
+
from .control_account_auth_interceptor import get_current_account_info
|
|
69
86
|
|
|
70
87
|
|
|
71
88
|
class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
@@ -77,14 +94,14 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
77
94
|
ffs_factory: FfsFactory,
|
|
78
95
|
objectstore_factory: ObjectStoreFactory,
|
|
79
96
|
is_simulation: bool,
|
|
80
|
-
|
|
81
|
-
artifact_provider:
|
|
97
|
+
authn_plugin: ControlAuthnPlugin,
|
|
98
|
+
artifact_provider: ArtifactProvider | None = None,
|
|
82
99
|
) -> None:
|
|
83
100
|
self.linkstate_factory = linkstate_factory
|
|
84
101
|
self.ffs_factory = ffs_factory
|
|
85
102
|
self.objectstore_factory = objectstore_factory
|
|
86
103
|
self.is_simulation = is_simulation
|
|
87
|
-
self.
|
|
104
|
+
self.authn_plugin = authn_plugin
|
|
88
105
|
self.artifact_provider = artifact_provider
|
|
89
106
|
|
|
90
107
|
def StartRun( # pylint: disable=too-many-locals
|
|
@@ -103,7 +120,8 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
103
120
|
)
|
|
104
121
|
return StartRunResponse()
|
|
105
122
|
|
|
106
|
-
flwr_aid =
|
|
123
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
124
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
107
125
|
override_config = user_config_from_proto(request.override_config)
|
|
108
126
|
federation_options = config_record_from_proto(request.federation_options)
|
|
109
127
|
fab_file = request.fab.content
|
|
@@ -115,8 +133,25 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
115
133
|
"Federation options doesn't contain key `num-supernodes`."
|
|
116
134
|
)
|
|
117
135
|
|
|
136
|
+
# Check (1) federation exists and (2) the flwr_aid is a member
|
|
137
|
+
federation = request.federation
|
|
138
|
+
|
|
139
|
+
if not state.federation_manager.exists(federation):
|
|
140
|
+
raise ValueError(f"Federation '{federation}' does not exist.")
|
|
141
|
+
|
|
142
|
+
if not state.federation_manager.has_member(flwr_aid, federation):
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"Account with ID '{flwr_aid}' is not a member of the "
|
|
145
|
+
f"federation '{federation}'. Please log in with another account "
|
|
146
|
+
"or request access to this federation."
|
|
147
|
+
)
|
|
148
|
+
|
|
118
149
|
# Create run
|
|
119
|
-
fab = Fab(
|
|
150
|
+
fab = Fab(
|
|
151
|
+
hashlib.sha256(fab_file).hexdigest(),
|
|
152
|
+
fab_file,
|
|
153
|
+
dict(request.fab.verifications),
|
|
154
|
+
)
|
|
120
155
|
fab_hash = ffs.put(fab.content, {})
|
|
121
156
|
if fab_hash != fab.hash_str:
|
|
122
157
|
raise RuntimeError(
|
|
@@ -129,6 +164,7 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
129
164
|
fab_version,
|
|
130
165
|
fab_hash,
|
|
131
166
|
override_config,
|
|
167
|
+
request.federation,
|
|
132
168
|
federation_options,
|
|
133
169
|
flwr_aid,
|
|
134
170
|
)
|
|
@@ -157,7 +193,10 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
157
193
|
# pylint: disable-next=broad-except
|
|
158
194
|
except Exception as e:
|
|
159
195
|
log(ERROR, "Could not start run: %s", str(e))
|
|
160
|
-
|
|
196
|
+
context.abort(
|
|
197
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
198
|
+
str(e),
|
|
199
|
+
)
|
|
161
200
|
|
|
162
201
|
log(INFO, "Created run %s", str(run_id))
|
|
163
202
|
return StartRunResponse(run_id=run_id)
|
|
@@ -177,12 +216,9 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
177
216
|
if not run:
|
|
178
217
|
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
|
179
218
|
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
_check_flwr_aid_in_run(
|
|
184
|
-
flwr_aid=flwr_aid, run=cast(Run, run), context=context
|
|
185
|
-
)
|
|
219
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
220
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
221
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=cast(Run, run), context=context)
|
|
186
222
|
|
|
187
223
|
after_timestamp = request.after_timestamp + 1e-6
|
|
188
224
|
while context.is_active():
|
|
@@ -218,20 +254,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
218
254
|
|
|
219
255
|
# Build a set of run IDs for `flwr ls --runs`
|
|
220
256
|
if not request.HasField("run_id"):
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
context.abort(
|
|
227
|
-
grpc.StatusCode.PERMISSION_DENIED,
|
|
228
|
-
"️⛔️ User authentication is enabled, but `flwr_aid` is None",
|
|
229
|
-
)
|
|
230
|
-
run_ids = state.get_run_ids(flwr_aid=flwr_aid)
|
|
231
|
-
else:
|
|
232
|
-
# If no `run_id` is specified and no user auth is enabled,
|
|
233
|
-
# return all run IDs
|
|
234
|
-
run_ids = state.get_run_ids(None)
|
|
257
|
+
# If no `run_id` is specified and account auth is enabled,
|
|
258
|
+
# return run IDs for the authenticated account
|
|
259
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
260
|
+
_check_flwr_aid_exists(flwr_aid, context)
|
|
261
|
+
run_ids = state.get_run_ids(flwr_aid=flwr_aid)
|
|
235
262
|
# Build a set of run IDs for `flwr ls --run-id <run_id>`
|
|
236
263
|
else:
|
|
237
264
|
# Retrieve run ID and run
|
|
@@ -241,13 +268,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
241
268
|
# Exit if `run_id` not found
|
|
242
269
|
if not run:
|
|
243
270
|
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
|
271
|
+
raise grpc.RpcError() # This line is unreachable
|
|
244
272
|
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
_check_flwr_aid_in_run(
|
|
249
|
-
flwr_aid=flwr_aid, run=cast(Run, run), context=context
|
|
250
|
-
)
|
|
273
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
274
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
275
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
251
276
|
|
|
252
277
|
run_ids = {run_id}
|
|
253
278
|
|
|
@@ -269,13 +294,11 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
269
294
|
# Exit if `run_id` not found
|
|
270
295
|
if not run:
|
|
271
296
|
context.abort(grpc.StatusCode.NOT_FOUND, RUN_ID_NOT_FOUND_MESSAGE)
|
|
297
|
+
raise grpc.RpcError() # This line is unreachable
|
|
272
298
|
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
_check_flwr_aid_in_run(
|
|
277
|
-
flwr_aid=flwr_aid, run=cast(Run, run), context=context
|
|
278
|
-
)
|
|
299
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
300
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
301
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
279
302
|
|
|
280
303
|
run_status = state.get_run_status({run_id})[run_id]
|
|
281
304
|
if run_status.status == Status.FINISHED:
|
|
@@ -284,11 +307,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
284
307
|
f"Run ID {run_id} is already finished",
|
|
285
308
|
)
|
|
286
309
|
|
|
310
|
+
# Update run status to finished:stopped
|
|
287
311
|
update_success = state.update_run_status(
|
|
288
312
|
run_id=run_id,
|
|
289
313
|
new_status=RunStatus(Status.FINISHED, SubStatus.STOPPED, ""),
|
|
290
314
|
)
|
|
291
315
|
|
|
316
|
+
# Delete the token associated with the run to stop further operations
|
|
317
|
+
state.delete_token(run_id)
|
|
318
|
+
|
|
292
319
|
if update_success:
|
|
293
320
|
message_ids: set[str] = state.get_message_ids_from_run_id(run_id)
|
|
294
321
|
|
|
@@ -305,22 +332,22 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
305
332
|
) -> GetLoginDetailsResponse:
|
|
306
333
|
"""Start login."""
|
|
307
334
|
log(INFO, "ControlServicer.GetLoginDetails")
|
|
308
|
-
if self.
|
|
335
|
+
if self.authn_plugin is None:
|
|
309
336
|
context.abort(
|
|
310
337
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
311
|
-
|
|
338
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
312
339
|
)
|
|
313
340
|
raise grpc.RpcError() # This line is unreachable
|
|
314
341
|
|
|
315
342
|
# Get login details
|
|
316
|
-
details = self.
|
|
343
|
+
details = self.authn_plugin.get_login_details()
|
|
317
344
|
|
|
318
345
|
# Return empty response if details is None
|
|
319
346
|
if details is None:
|
|
320
347
|
return GetLoginDetailsResponse()
|
|
321
348
|
|
|
322
349
|
return GetLoginDetailsResponse(
|
|
323
|
-
|
|
350
|
+
authn_type=details.authn_type,
|
|
324
351
|
device_code=details.device_code,
|
|
325
352
|
verification_uri_complete=details.verification_uri_complete,
|
|
326
353
|
expires_in=details.expires_in,
|
|
@@ -332,15 +359,15 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
332
359
|
) -> GetAuthTokensResponse:
|
|
333
360
|
"""Get auth token."""
|
|
334
361
|
log(INFO, "ControlServicer.GetAuthTokens")
|
|
335
|
-
if self.
|
|
362
|
+
if self.authn_plugin is None:
|
|
336
363
|
context.abort(
|
|
337
364
|
grpc.StatusCode.UNIMPLEMENTED,
|
|
338
|
-
|
|
365
|
+
NO_ACCOUNT_AUTH_MESSAGE,
|
|
339
366
|
)
|
|
340
367
|
raise grpc.RpcError() # This line is unreachable
|
|
341
368
|
|
|
342
369
|
# Get auth tokens
|
|
343
|
-
credentials = self.
|
|
370
|
+
credentials = self.authn_plugin.get_auth_tokens(request.device_code)
|
|
344
371
|
|
|
345
372
|
# Return empty response if credentials is None
|
|
346
373
|
if credentials is None:
|
|
@@ -383,15 +410,160 @@ class ControlServicer(control_pb2_grpc.ControlServicer):
|
|
|
383
410
|
grpc.StatusCode.FAILED_PRECONDITION, PULL_UNFINISHED_RUN_MESSAGE
|
|
384
411
|
)
|
|
385
412
|
|
|
386
|
-
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
413
|
+
# Check if `flwr_aid` matches the run's `flwr_aid`
|
|
414
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
415
|
+
_check_flwr_aid_in_run(flwr_aid=flwr_aid, run=run, context=context)
|
|
390
416
|
|
|
391
417
|
# Call artifact provider
|
|
392
418
|
download_url = self.artifact_provider.get_url(run_id)
|
|
393
419
|
return PullArtifactsResponse(url=download_url)
|
|
394
420
|
|
|
421
|
+
def RegisterNode(
|
|
422
|
+
self, request: RegisterNodeRequest, context: grpc.ServicerContext
|
|
423
|
+
) -> RegisterNodeResponse:
|
|
424
|
+
"""Add a SuperNode."""
|
|
425
|
+
log(INFO, "ControlServicer.RegisterNode")
|
|
426
|
+
|
|
427
|
+
# Verify public key
|
|
428
|
+
try:
|
|
429
|
+
# Attempt to deserialize public key
|
|
430
|
+
pub_key = bytes_to_public_key(request.public_key)
|
|
431
|
+
# Check if it's a NIST EC curve public key
|
|
432
|
+
if not uses_nist_ec_curve(pub_key):
|
|
433
|
+
err_msg = "The provided public key is not a NIST EC curve public key."
|
|
434
|
+
log(ERROR, "%s", err_msg)
|
|
435
|
+
raise ValueError(err_msg)
|
|
436
|
+
except (ValueError, AttributeError) as err:
|
|
437
|
+
log(ERROR, "%s", err)
|
|
438
|
+
context.abort(grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_NOT_VALID)
|
|
439
|
+
|
|
440
|
+
# Init link state
|
|
441
|
+
state = self.linkstate_factory.state()
|
|
442
|
+
node_id = 0
|
|
443
|
+
|
|
444
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
445
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
446
|
+
# Account name exists if `flwr_aid` exists
|
|
447
|
+
account_name = cast(str, get_current_account_info().account_name)
|
|
448
|
+
try:
|
|
449
|
+
node_id = state.create_node(
|
|
450
|
+
owner_aid=flwr_aid,
|
|
451
|
+
owner_name=account_name,
|
|
452
|
+
public_key=request.public_key,
|
|
453
|
+
heartbeat_interval=HEARTBEAT_DEFAULT_INTERVAL,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
except ValueError:
|
|
457
|
+
# Public key already in use
|
|
458
|
+
log(ERROR, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE)
|
|
459
|
+
context.abort(
|
|
460
|
+
grpc.StatusCode.FAILED_PRECONDITION, PUBLIC_KEY_ALREADY_IN_USE_MESSAGE
|
|
461
|
+
)
|
|
462
|
+
log(INFO, "[ControlServicer.RegisterNode] Created node_id=%s", node_id)
|
|
463
|
+
|
|
464
|
+
return RegisterNodeResponse(node_id=node_id)
|
|
465
|
+
|
|
466
|
+
def UnregisterNode(
|
|
467
|
+
self, request: UnregisterNodeRequest, context: grpc.ServicerContext
|
|
468
|
+
) -> UnregisterNodeResponse:
|
|
469
|
+
"""Remove a SuperNode."""
|
|
470
|
+
log(INFO, "ControlServicer.UnregisterNode")
|
|
471
|
+
|
|
472
|
+
# Init link state
|
|
473
|
+
state = self.linkstate_factory.state()
|
|
474
|
+
|
|
475
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
476
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
477
|
+
try:
|
|
478
|
+
state.delete_node(owner_aid=flwr_aid, node_id=request.node_id)
|
|
479
|
+
except ValueError:
|
|
480
|
+
log(ERROR, NODE_NOT_FOUND_MESSAGE)
|
|
481
|
+
context.abort(grpc.StatusCode.NOT_FOUND, NODE_NOT_FOUND_MESSAGE)
|
|
482
|
+
|
|
483
|
+
return UnregisterNodeResponse()
|
|
484
|
+
|
|
485
|
+
def ListNodes(
|
|
486
|
+
self, request: ListNodesRequest, context: grpc.ServicerContext
|
|
487
|
+
) -> ListNodesResponse:
|
|
488
|
+
"""List all SuperNodes."""
|
|
489
|
+
log(INFO, "ControlServicer.ListNodes")
|
|
490
|
+
|
|
491
|
+
if self.is_simulation:
|
|
492
|
+
log(ERROR, "ListNodes is not available in simulation mode.")
|
|
493
|
+
context.abort(
|
|
494
|
+
grpc.StatusCode.UNIMPLEMENTED,
|
|
495
|
+
"ListNodes is not available in simulation mode.",
|
|
496
|
+
)
|
|
497
|
+
raise grpc.RpcError() # This line is unreachable
|
|
498
|
+
|
|
499
|
+
nodes_info: Sequence[NodeInfo] = []
|
|
500
|
+
# Init link state
|
|
501
|
+
state = self.linkstate_factory.state()
|
|
502
|
+
|
|
503
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
504
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
505
|
+
# Retrieve all nodes for the account
|
|
506
|
+
nodes_info = state.get_node_info(owner_aids=[flwr_aid])
|
|
507
|
+
|
|
508
|
+
return ListNodesResponse(nodes_info=nodes_info, now=now().isoformat())
|
|
509
|
+
|
|
510
|
+
def ListFederations(
|
|
511
|
+
self, request: ListFederationsRequest, context: grpc.ServicerContext
|
|
512
|
+
) -> ListFederationsResponse:
|
|
513
|
+
"""List all SuperNodes."""
|
|
514
|
+
log(INFO, "ControlServicer.ListFederations")
|
|
515
|
+
|
|
516
|
+
# Init link state
|
|
517
|
+
state = self.linkstate_factory.state()
|
|
518
|
+
|
|
519
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
520
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
521
|
+
|
|
522
|
+
# Get federations the account is a member of
|
|
523
|
+
federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
|
|
524
|
+
|
|
525
|
+
return ListFederationsResponse(
|
|
526
|
+
federations=[Federation(name=fed) for fed in federations]
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
def ShowFederation(
|
|
530
|
+
self, request: ShowFederationRequest, context: grpc.ServicerContext
|
|
531
|
+
) -> ShowFederationResponse:
|
|
532
|
+
"""Show details of a specific Federation."""
|
|
533
|
+
log(INFO, "ControlServicer.ShowFederation")
|
|
534
|
+
|
|
535
|
+
# Init link state
|
|
536
|
+
state = self.linkstate_factory.state()
|
|
537
|
+
|
|
538
|
+
flwr_aid = get_current_account_info().flwr_aid
|
|
539
|
+
flwr_aid = _check_flwr_aid_exists(flwr_aid, context)
|
|
540
|
+
|
|
541
|
+
# Get federations the account is a member of
|
|
542
|
+
federations = state.federation_manager.get_federations(flwr_aid=flwr_aid)
|
|
543
|
+
|
|
544
|
+
# Ensure flwr_aid is a member of the requested federation
|
|
545
|
+
federation = request.federation_name
|
|
546
|
+
if federation not in federations:
|
|
547
|
+
context.abort(
|
|
548
|
+
grpc.StatusCode.FAILED_PRECONDITION,
|
|
549
|
+
f"Federation '{federation}' does not exist or you are "
|
|
550
|
+
"not a member of it.",
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# Fetch federation details
|
|
554
|
+
details = state.federation_manager.get_details(federation)
|
|
555
|
+
|
|
556
|
+
# Build Federation proto object
|
|
557
|
+
federation_proto = Federation(
|
|
558
|
+
name=federation,
|
|
559
|
+
member_aids=details.member_aids,
|
|
560
|
+
nodes=details.nodes,
|
|
561
|
+
runs=[run_to_proto(run) for run in details.runs],
|
|
562
|
+
)
|
|
563
|
+
return ShowFederationResponse(
|
|
564
|
+
federation=federation_proto, now=now().isoformat()
|
|
565
|
+
)
|
|
566
|
+
|
|
395
567
|
|
|
396
568
|
def _create_list_runs_response(
|
|
397
569
|
run_ids: set[int], state: LinkState, store: ObjectStore
|
|
@@ -410,29 +582,33 @@ def _create_list_runs_response(
|
|
|
410
582
|
)
|
|
411
583
|
|
|
412
584
|
|
|
413
|
-
def
|
|
414
|
-
|
|
415
|
-
) -> None:
|
|
416
|
-
"""Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
|
|
417
|
-
# `flwr_aid` must not be None. Abort if it is None.
|
|
585
|
+
def _check_flwr_aid_exists(flwr_aid: str | None, context: grpc.ServicerContext) -> str:
|
|
586
|
+
"""Guard clause to check if `flwr_aid` exists."""
|
|
418
587
|
if flwr_aid is None:
|
|
419
588
|
context.abort(
|
|
420
589
|
grpc.StatusCode.PERMISSION_DENIED,
|
|
421
|
-
"️⛔️
|
|
590
|
+
"️⛔️ Failed to fetch the account information.",
|
|
422
591
|
)
|
|
592
|
+
raise RuntimeError # This line is unreachable
|
|
593
|
+
return flwr_aid
|
|
423
594
|
|
|
595
|
+
|
|
596
|
+
def _check_flwr_aid_in_run(
|
|
597
|
+
flwr_aid: str | None, run: Run, context: grpc.ServicerContext
|
|
598
|
+
) -> None:
|
|
599
|
+
"""Guard clause to check if `flwr_aid` matches the run's `flwr_aid`."""
|
|
600
|
+
_check_flwr_aid_exists(flwr_aid, context)
|
|
424
601
|
# `run.flwr_aid` must not be an empty string. Abort if it is empty.
|
|
425
602
|
run_flwr_aid = run.flwr_aid
|
|
426
603
|
if not run_flwr_aid:
|
|
427
604
|
context.abort(
|
|
428
605
|
grpc.StatusCode.PERMISSION_DENIED,
|
|
429
|
-
"⛔️
|
|
430
|
-
"with a `flwr_aid`.",
|
|
606
|
+
"⛔️ Run is not associated with a `flwr_aid`.",
|
|
431
607
|
)
|
|
432
608
|
|
|
433
609
|
# Exit if `flwr_aid` does not match the run's `flwr_aid`
|
|
434
610
|
if run_flwr_aid != flwr_aid:
|
|
435
611
|
context.abort(
|
|
436
612
|
grpc.StatusCode.PERMISSION_DENIED,
|
|
437
|
-
"⛔️ Run ID does not belong to the
|
|
613
|
+
"⛔️ Run ID does not belong to the account",
|
|
438
614
|
)
|
|
@@ -18,14 +18,12 @@
|
|
|
18
18
|
import argparse
|
|
19
19
|
from logging import DEBUG, INFO, WARN
|
|
20
20
|
from pathlib import Path
|
|
21
|
-
from typing import Optional
|
|
22
21
|
|
|
22
|
+
import yaml
|
|
23
23
|
from cryptography.exceptions import UnsupportedAlgorithm
|
|
24
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
25
|
-
from cryptography.hazmat.primitives.serialization import
|
|
26
|
-
|
|
27
|
-
load_ssh_public_key,
|
|
28
|
-
)
|
|
24
|
+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
|
|
25
|
+
from cryptography.hazmat.primitives.serialization import load_ssh_private_key
|
|
26
|
+
from cryptography.hazmat.primitives.serialization.ssh import load_ssh_public_key
|
|
29
27
|
|
|
30
28
|
from flwr.common import EventType, event
|
|
31
29
|
from flwr.common.args import try_obtain_root_certificates
|
|
@@ -61,9 +59,19 @@ def flower_supernode() -> None:
|
|
|
61
59
|
"Ignoring `--flwr-dir`.",
|
|
62
60
|
)
|
|
63
61
|
|
|
62
|
+
trusted_entities = _try_obtain_trusted_entities(args.trusted_entities)
|
|
63
|
+
if trusted_entities:
|
|
64
|
+
_validate_public_keys_ed25519(trusted_entities)
|
|
64
65
|
root_certificates = try_obtain_root_certificates(args, args.superlink)
|
|
65
66
|
authentication_keys = _try_setup_client_authentication(args)
|
|
66
67
|
|
|
68
|
+
# Warn if authentication keys are provided but transport is not grpc-rere
|
|
69
|
+
if authentication_keys is not None and args.transport != TRANSPORT_TYPE_GRPC_RERE:
|
|
70
|
+
log(
|
|
71
|
+
WARN,
|
|
72
|
+
"SuperNode Authentication is only supported with the grpc-rere transport.",
|
|
73
|
+
)
|
|
74
|
+
|
|
67
75
|
log(DEBUG, "Isolation mode: %s", args.isolation)
|
|
68
76
|
|
|
69
77
|
start_client_internal(
|
|
@@ -81,6 +89,7 @@ def flower_supernode() -> None:
|
|
|
81
89
|
isolation=args.isolation,
|
|
82
90
|
clientappio_api_address=args.clientappio_api_address,
|
|
83
91
|
health_server_address=args.health_server_address,
|
|
92
|
+
trusted_entities=trusted_entities,
|
|
84
93
|
)
|
|
85
94
|
|
|
86
95
|
|
|
@@ -120,6 +129,18 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser:
|
|
|
120
129
|
help="ClientAppIo API (gRPC) server address (IPv4, IPv6, or a domain name). "
|
|
121
130
|
f"By default, it is set to {CLIENTAPPIO_API_DEFAULT_SERVER_ADDRESS}.",
|
|
122
131
|
)
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
"--trusted-entities",
|
|
134
|
+
type=Path,
|
|
135
|
+
default=None,
|
|
136
|
+
metavar="YAML_FILE",
|
|
137
|
+
help=(
|
|
138
|
+
"Path to a YAML file defining trusted entities. "
|
|
139
|
+
"The file must map public key IDs to public keys. "
|
|
140
|
+
"Example: { fpk_UUID1: 'ssh-ed25519 <key1> [comment1]', "
|
|
141
|
+
"fpk_UUID2: 'ssh-ed25519 <key2> [comment2]' }"
|
|
142
|
+
),
|
|
143
|
+
)
|
|
123
144
|
add_args_health(parser)
|
|
124
145
|
|
|
125
146
|
return parser
|
|
@@ -188,12 +209,12 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
188
209
|
parser.add_argument(
|
|
189
210
|
"--auth-supernode-private-key",
|
|
190
211
|
type=str,
|
|
191
|
-
help="
|
|
212
|
+
help="Path to the SuperNode's private key to enable authentication.",
|
|
192
213
|
)
|
|
193
214
|
parser.add_argument(
|
|
194
215
|
"--auth-supernode-public-key",
|
|
195
216
|
type=str,
|
|
196
|
-
help="
|
|
217
|
+
help="This argument is deprecated and will be removed in a future release.",
|
|
197
218
|
)
|
|
198
219
|
parser.add_argument(
|
|
199
220
|
"--node-config",
|
|
@@ -206,13 +227,10 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
|
|
|
206
227
|
|
|
207
228
|
def _try_setup_client_authentication(
|
|
208
229
|
args: argparse.Namespace,
|
|
209
|
-
) ->
|
|
210
|
-
if not args.auth_supernode_private_key
|
|
230
|
+
) -> tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] | None:
|
|
231
|
+
if not args.auth_supernode_private_key:
|
|
211
232
|
return None
|
|
212
233
|
|
|
213
|
-
if not args.auth_supernode_private_key or not args.auth_supernode_public_key:
|
|
214
|
-
flwr_exit(ExitCode.SUPERNODE_NODE_AUTH_KEYS_REQUIRED)
|
|
215
|
-
|
|
216
234
|
try:
|
|
217
235
|
ssh_private_key = load_ssh_private_key(
|
|
218
236
|
Path(args.auth_supernode_private_key).read_bytes(),
|
|
@@ -222,23 +240,53 @@ def _try_setup_client_authentication(
|
|
|
222
240
|
raise ValueError()
|
|
223
241
|
except (ValueError, UnsupportedAlgorithm):
|
|
224
242
|
flwr_exit(
|
|
225
|
-
ExitCode.
|
|
243
|
+
ExitCode.SUPERNODE_NODE_AUTH_KEY_INVALID,
|
|
226
244
|
"Unable to parse the private key file.",
|
|
227
245
|
)
|
|
228
246
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
if args.auth_supernode_public_key:
|
|
248
|
+
log(
|
|
249
|
+
WARN,
|
|
250
|
+
"The `--auth-supernode-public-key` flag is deprecated and will be "
|
|
251
|
+
"removed in a future release. The public key is now derived from the "
|
|
252
|
+
"private key provided by `--auth-supernode-private-key`.",
|
|
232
253
|
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
254
|
+
return ssh_private_key, ssh_private_key.public_key()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _try_obtain_trusted_entities(
|
|
258
|
+
trusted_entities_path: Path | None,
|
|
259
|
+
) -> dict[str, str] | None:
|
|
260
|
+
"""Validate and return the trust entities."""
|
|
261
|
+
if not trusted_entities_path:
|
|
262
|
+
return None
|
|
263
|
+
if not trusted_entities_path.is_file():
|
|
236
264
|
flwr_exit(
|
|
237
|
-
ExitCode.
|
|
238
|
-
"
|
|
265
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
266
|
+
"Path argument `--trusted-entities` does not point to a file.",
|
|
239
267
|
)
|
|
268
|
+
try:
|
|
269
|
+
with trusted_entities_path.open("r", encoding="utf-8") as f:
|
|
270
|
+
trusted_entities = yaml.safe_load(f)
|
|
271
|
+
if not isinstance(trusted_entities, dict):
|
|
272
|
+
raise ValueError("Invalid trusted entities format.")
|
|
273
|
+
except (yaml.YAMLError, ValueError) as e:
|
|
274
|
+
flwr_exit(
|
|
275
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
276
|
+
f"Failed to read YAML file '{trusted_entities_path}': {e}",
|
|
277
|
+
)
|
|
278
|
+
return trusted_entities
|
|
240
279
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
280
|
+
|
|
281
|
+
def _validate_public_keys_ed25519(trusted_entities: dict[str, str]) -> None:
|
|
282
|
+
"""Validate public keys for the trust entities are Ed25519."""
|
|
283
|
+
for public_key_id in trusted_entities.keys():
|
|
284
|
+
verifier_public_key = load_ssh_public_key(
|
|
285
|
+
trusted_entities[public_key_id].encode("utf-8")
|
|
286
|
+
)
|
|
287
|
+
if not isinstance(verifier_public_key, ed25519.Ed25519PublicKey):
|
|
288
|
+
flwr_exit(
|
|
289
|
+
ExitCode.SUPERNODE_INVALID_TRUSTED_ENTITIES,
|
|
290
|
+
"The provided public key associated with "
|
|
291
|
+
f"trusted entity {public_key_id} is not Ed25519.",
|
|
292
|
+
)
|