Skip to content

Commit

Permalink
refactor(framework) Update CLI auth flags (#3503)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel J. Beutel <daniel@flower.ai>
  • Loading branch information
danielnugraha and danieljanes authored May 24, 2024
1 parent 05ad7df commit 83e9ba4
Show file tree
Hide file tree
Showing 13 changed files with 138 additions and 100 deletions.
17 changes: 10 additions & 7 deletions doc/source/how-to-authenticate-supernodes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Flower node authentication works similar to how GitHub SSH authentication works:
* Shared secret is used to compute the HMAC value of the message sent from SuperNode to SuperLink as a token
* SuperLink verifies the token

We recommend you to check out the complete `code example <https://github.com/adap/flower/tree/main/examples/flower-client-authentication>`_ demonstrating federated learning with Flower in an authenticated setting.
We recommend you to check out the complete `code example <https://github.com/adap/flower/tree/main/examples/flower-authentication>`_ demonstrating federated learning with Flower in an authenticated setting.

.. note::
This guide covers a preview feature that might change in future versions of Flower.
Expand All @@ -29,15 +29,17 @@ Use the following terminal command to start a Flower :code:`SuperNode` that has
flower-superlink
--certificates certificates/ca.crt certificates/server.pem certificates/server.key
--require-client-authentication ./keys/client_public_keys.csv ./keys/server_credentials ./keys/server_credentials.pub
--auth-list-public-keys keys/client_public_keys.csv
--auth-superlink-private-key keys/server_credentials
--auth-superlink-public-key keys/server_credentials.pub
Let's break down the :code:`--require-client-authentication` flag:
Let's break down the authentication flags:

1. The first argument is a path to a CSV file storing all known node public keys. You need to store all known node public keys that are allowed to participate in a federation in one CSV file (:code:`.csv`).
1. The first flag :code:`--auth-list-public-keys` expects a path to a CSV file storing all known node public keys. You need to store all known node public keys that are allowed to participate in a federation in one CSV file (:code:`.csv`).

A valid CSV file storing known node public keys should list the keys in OpenSSH format, separated by commas and without any comments. For an example, refer to our code sample, which contains a CSV file with two known node public keys.

2. The second and third arguments are paths to the server's private and public keys. For development purposes, you can generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b 384`.
2. The second and third flags :code:`--auth-superlink-private-key` and :code:`--auth-superlink-public-key` expect paths to the server's private and public keys. For development purposes, you can generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b 384`.

.. note::
In Flower 1.9, there is no support for dynamically removing, editing, or adding known node public keys to the SuperLink.
Expand All @@ -56,9 +58,10 @@ Use the following terminal command to start an authenticated :code:`SuperNode`:
flower-client-app client:app
--root-certificates certificates/ca.crt
--server 127.0.0.1:9092
--authentication-keys ./keys/client_credentials ./keys/client_credentials.pub
--auth-supernode-private-key keys/client_credentials
--auth-supernode-public-key keys/client_credentials.pub
The :code:`--authentication-keys` flag expects two arguments: a path to the node's private key file and a path to the node's public key file. For development purposes, you can generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b 384`.
The :code:`--auth-supernode-private-key` flag expects a path to the node's private key file and the :code:`--auth-supernode-public-key` flag expects a path to the node's public key file. For development purposes, you can generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b 384`.


Security notice
Expand Down
7 changes: 3 additions & 4 deletions e2e/test_driver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ case "$2" in
db_arg="--database :flwr-in-memory-state:"
server_arg="--certificates certificates/ca.crt certificates/server.pem certificates/server.key"
client_arg="--root-certificates certificates/ca.crt"
server_auth="--require-client-authentication keys/client_public_keys.csv keys/server_credentials keys/server_credentials.pub"
client_auth_1="--authentication-keys keys/client_credentials_1 keys/client_credentials_1.pub"
client_auth_2="--authentication-keys keys/client_credentials_2 keys/client_credentials_2.pub"
server_auth="--auth-list-public-keys keys/client_public_keys.csv --auth-superlink-private-key keys/server_credentials --auth-superlink-public-key keys/server_credentials.pub"
client_auth_1="--auth-supernode-private-key keys/client_credentials_1 --auth-supernode-public-key keys/client_credentials_1.pub"
client_auth_2="--auth-supernode-private-key keys/client_credentials_2 --auth-supernode-public-key keys/client_credentials_2.pub"
;;
*)
rest_arg=""
Expand Down Expand Up @@ -84,4 +84,3 @@ if [[ "$res" = "0" ]];
then echo "Training worked correctly"; kill $cl1_pid; kill $cl2_pid; kill $sl_pid;
else echo "Training had an issue" && exit 1;
fi

Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Flower Client Authentication with PyTorch 🧪
# Flower Authentication with PyTorch 🧪

> 🧪 = This example covers experimental features that might change in future versions of Flower
> Please consult the regular PyTorch code examples ([quickstart](https://github.com/adap/flower/tree/main/examples/quickstart-pytorch), [advanced](https://github.com/adap/flower/tree/main/examples/advanced-pytorch)) to learn how to use Flower with PyTorch.
The following steps describe how to start a long-running Flower server (SuperLink) and a long-running Flower client (SuperNode) with client authentication enabled.
The following steps describe how to start a long-running Flower server (SuperLink) and a long-running Flower client (SuperNode) with authentication enabled.

## Project Setup

Start by cloning the example project. We prepared a single-line command that you can copy into your shell which will checkout the example for you:

```shell
git clone --depth=1 https://github.com/adap/flower.git _tmp && mv _tmp/examples/flower-client-authentication . && rm -rf _tmp && cd flower-client-authentication
git clone --depth=1 https://github.com/adap/flower.git _tmp && mv _tmp/examples/flower-authentication . && rm -rf _tmp && cd flower-authentication
```

This will create a new directory called `flower-client-authentication` with the following project structure:
This will create a new directory called `flower-authentication` with the following project structure:

```bash
$ tree .
Expand Down Expand Up @@ -62,26 +62,28 @@ The script also generates a CSV file that includes each of the generated (client

## Start the long-running Flower server (SuperLink)

To start a long-running Flower server and enable client authentication is very easy; all you need to do is type
`--require-client-authentication` followed by the path to the known `client_public_keys.csv`, server's private key
`server_credentials`, and server's public key `server_credentials.pub`. Notice that you can only enable client
authentication with a secure TLS connection.
To start a long-running Flower server (SuperLink) and enable authentication is very easy; all you need to do is type
`--auth-list-public-keys` containing file path to the known `client_public_keys.csv`, `--auth-superlink-private-key`
containing file path to the SuperLink's private key `server_credentials`, and `--auth-superlink-public-key` containing file path to the SuperLink's public key `server_credentials.pub`. Notice that you can only enable authentication with a secure TLS connection.

```bash
flower-superlink \
--certificates certificates/ca.crt certificates/server.pem certificates/server.key \
--require-client-authentication keys/client_public_keys.csv keys/server_credentials keys/server_credentials.pub
--auth-list-public-keys keys/client_public_keys.csv \
--auth-superlink-private-key keys/server_credentials \
--auth-superlink-public-key keys/server_credentials.pub
```

## Start the long-running Flower client (SuperNode)

In a new terminal window, start the first long-running Flower client:
In a new terminal window, start the first long-running Flower client (SuperNode):

```bash
flower-client-app client:app \
--root-certificates certificates/ca.crt \
--server 127.0.0.1:9092 \
--authentication-keys keys/client_credentials_1 keys/client_credentials_1.pub
--auth-supernode-private-key keys/client_credentials_1 \
--auth-supernode-public-key keys/client_credentials_1.pub
```

In yet another new terminal window, start the second long-running Flower client:
Expand All @@ -90,7 +92,8 @@ In yet another new terminal window, start the second long-running Flower client:
flower-client-app client:app \
--root-certificates certificates/ca.crt \
--server 127.0.0.1:9092 \
--authentication-keys keys/client_credentials_2 keys/client_credentials_2.pub
--auth-supernode-private-key keys/client_credentials_2 \
--auth-supernode-public-key keys/client_credentials_2.pub
```

If you generated more than 2 client credentials, you can add more clients by opening new terminal windows and running the command
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
64 changes: 41 additions & 23 deletions src/py/flwr/client/supernode/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pathlib import Path
from typing import Callable, Optional, Tuple

from cryptography.exceptions import UnsupportedAlgorithm
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import (
load_ssh_private_key,
Expand All @@ -31,9 +32,6 @@
from flwr.common.exit_handlers import register_exit_handlers
from flwr.common.logger import log
from flwr.common.object_ref import load_app, validate
from flwr.common.secure_aggregation.crypto.symmetric_encryption import (
ssh_types_to_elliptic_curve,
)

from ..app import _start_client_internal

Expand Down Expand Up @@ -242,40 +240,60 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None:
" Default: current working directory.",
)
parser.add_argument(
"--authentication-keys",
nargs=2,
metavar=("CLIENT_PRIVATE_KEY", "CLIENT_PUBLIC_KEY"),
"--auth-supernode-private-key",
type=str,
help="The SuperNode's private key (as a path str) to enable authentication.",
)
parser.add_argument(
"--auth-supernode-public-key",
type=str,
help="Provide two file paths: (1) the client's private "
"key file, and (2) the client's public key file.",
help="The SuperNode's public key (as a path str) to enable authentication.",
)


def _try_setup_client_authentication(
args: argparse.Namespace,
) -> Optional[Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]]:
if not args.authentication_keys:
if not args.auth_supernode_private_key and not args.auth_supernode_public_key:
return None

ssh_private_key = load_ssh_private_key(
Path(args.authentication_keys[0]).read_bytes(),
None,
)
ssh_public_key = load_ssh_public_key(Path(args.authentication_keys[1]).read_bytes())
if not args.auth_supernode_private_key or not args.auth_supernode_public_key:
sys.exit(
"Authentication requires file paths to both "
"'--auth-supernode-private-key' and '--auth-supernode-public-key'"
"to be provided (providing only one of them is not sufficient)."
)

try:
ssh_private_key = load_ssh_private_key(
Path(args.auth_supernode_private_key).read_bytes(),
None,
)
if not isinstance(ssh_private_key, ec.EllipticCurvePrivateKey):
raise ValueError()
except (ValueError, UnsupportedAlgorithm):
sys.exit(
"Error: Unable to parse the private key file in "
"'--auth-supernode-private-key'. Authentication requires elliptic "
"curve private and public key pair. Please ensure that the file "
"path points to a valid private key file and try again."
)

try:
client_private_key, client_public_key = ssh_types_to_elliptic_curve(
ssh_private_key, ssh_public_key
ssh_public_key = load_ssh_public_key(
Path(args.auth_supernode_public_key).read_bytes()
)
except TypeError:
if not isinstance(ssh_public_key, ec.EllipticCurvePublicKey):
raise ValueError()
except (ValueError, UnsupportedAlgorithm):
sys.exit(
"The file paths provided could not be read as a private and public "
"key pair. Client authentication requires an elliptic curve public and "
"private key pair. Please provide the file paths containing elliptic "
"curve private and public keys to '--authentication-keys'."
"Error: Unable to parse the public key file in "
"'--auth-supernode-public-key'. Authentication requires elliptic "
"curve private and public key pair. Please ensure that the file "
"path points to a valid public key file and try again."
)

return (
client_private_key,
client_public_key,
ssh_private_key,
ssh_public_key,
)
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,3 @@ def verify_hmac(key: bytes, message: bytes, hmac_value: bytes) -> bool:
return True
except InvalidSignature:
return False


def ssh_types_to_elliptic_curve(
private_key: serialization.SSHPrivateKeyTypes,
public_key: serialization.SSHPublicKeyTypes,
) -> Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey]:
"""Cast SSH key types to elliptic curve."""
if isinstance(private_key, ec.EllipticCurvePrivateKey) and isinstance(
public_key, ec.EllipticCurvePublicKey
):
return (private_key, public_key)

raise TypeError(
"The provided key is not an EllipticCurvePrivateKey or EllipticCurvePublicKey"
)
Loading

0 comments on commit 83e9ba4

Please sign in to comment.