diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index 03ceb878f33b..9a850c904266 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -50,7 +50,6 @@ TRANSPORT_TYPE_GRPC_ADAPTER, TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_REST, - Status, ) from flwr.common.exit_handlers import register_exit_handlers from flwr.common.logger import log @@ -58,7 +57,6 @@ private_key_to_bytes, public_key_to_bytes, ) -from flwr.common.typing import RunStatus from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611 add_FleetServicer_to_server, ) @@ -345,7 +343,7 @@ def run_superlink() -> None: # Scheduler thread scheduler_th = threading.Thread( target=_flwr_serverapp_scheduler, - args=(state_factory, args.driver_api_address), + args=(state_factory, args.driver_api_address, args.ssl_ca_certfile), ) scheduler_th.start() bckg_threads.append(scheduler_th) @@ -367,7 +365,9 @@ def run_superlink() -> None: def _flwr_serverapp_scheduler( - state_factory: LinkStateFactory, driver_api_address: str + state_factory: LinkStateFactory, + driver_api_address: str, + ssl_ca_certfile: Optional[str], ) -> None: log(DEBUG, "Started flwr-serverapp scheduler thread.") @@ -380,10 +380,6 @@ def _flwr_serverapp_scheduler( if pending_run_id: - # Set run as starting - state.update_run_status( - run_id=pending_run_id, new_status=RunStatus(Status.STARTING, "", "") - ) log( INFO, "Launching `flwr-serverapp` subprocess with run-id %d. " @@ -399,6 +395,12 @@ def _flwr_serverapp_scheduler( "--run-id", str(pending_run_id), ] + if ssl_ca_certfile: + command.append("--root-certificates") + command.append(ssl_ca_certfile) + else: + command.append("--insecure") + subprocess.run( command, stdout=None, diff --git a/src/py/flwr/server/serverapp/app.py b/src/py/flwr/server/serverapp/app.py index 22f9473087fb..ef95e34ee9d0 100644 --- a/src/py/flwr/server/serverapp/app.py +++ b/src/py/flwr/server/serverapp/app.py @@ -15,7 +15,10 @@ """Flower ServerApp process.""" import argparse -from logging import DEBUG, INFO +import sys +from logging import DEBUG, INFO, WARN +from os.path import isfile +from pathlib import Path from typing import Optional from flwr.common.logger import log @@ -41,8 +44,35 @@ def flwr_serverapp() -> None: help="Id of the Run this process should start. If not supplied, this " "function will request a pending run to the LinkState.", ) + parser.add_argument( + "--flwr-dir", + default=None, + help="""The path containing installed Flower Apps. + By default, this value is equal to: + + - `$FLWR_HOME/` if `$FLWR_HOME` is defined + - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined + - `$HOME/.flwr/` in all other cases + """, + ) + parser.add_argument( + "--insecure", + action="store_true", + help="Run the server without HTTPS, regardless of whether certificate " + "paths are provided. By default, the server runs with HTTPS enabled. " + "Use this flag only if you understand the risks.", + ) + parser.add_argument( + "--root-certificates", + metavar="ROOT_CERT", + type=str, + help="Specifies the path to the PEM-encoded root certificate file for " + "establishing secure HTTPS connections.", + ) args = parser.parse_args() + certificates = _try_obtain_certificates(args) + log( DEBUG, "Staring isolated `ServerApp` connected to SuperLink DriverAPI at %s " @@ -50,29 +80,62 @@ def flwr_serverapp() -> None: args.superlink, args.run_id, ) - run_serverapp(superlink=args.superlink, run_id=args.run_id) + run_serverapp( + superlink=args.superlink, + run_id=args.run_id, + flwr_dir_=args.flwr_dir, + certificates=certificates, + ) + + +def _try_obtain_certificates( + args: argparse.Namespace, +) -> Optional[bytes]: + + if args.insecure: + if args.root_certificates is not None: + sys.exit( + "Conflicting options: The '--insecure' flag disables HTTPS, " + "but '--root-certificates' was also specified. Please remove " + "the '--root-certificates' option when running in insecure mode, " + "or omit '--insecure' to use HTTPS." + ) + log( + WARN, + "Option `--insecure` was set. Starting insecure HTTP channel to %s.", + args.superlink, + ) + root_certificates = None + else: + # Load the certificates if provided, or load the system certificates + if not isfile(args.root_certificates): + sys.exit("Path argument `--root-certificates` does not point to a file.") + root_certificates = Path(args.root_certificates).read_bytes() + log( + DEBUG, + "Starting secure HTTPS channel to %s " + "with the following certificates: %s.", + args.superlink, + args.root_certificates, + ) + return root_certificates def run_serverapp( # pylint: disable=R0914 superlink: str, run_id: Optional[int] = None, + flwr_dir_: Optional[str] = None, + certificates: Optional[bytes] = None, ) -> None: - """Run Flower ServerApp process. - - Parameters - ---------- - superlink : str - Address of SuperLink - run_id : Optional[int] (default: None) - Unique identifier of a Run registered at the LinkState. If not supplied, - this function will request a pending run to the LinkState. - """ + """Run Flower ServerApp process.""" _ = GrpcDriver( run_id=run_id if run_id else 0, driver_service_address=superlink, - root_certificates=None, + root_certificates=certificates, ) + log(INFO, "%s", flwr_dir_) + # Then, GetServerInputs # Then, run ServerApp