Skip to content

Commit

Permalink
Modify example to start secure server (adap#847)
Browse files Browse the repository at this point in the history
  • Loading branch information
tanertopal authored Jun 13, 2022
1 parent b77cb7c commit 68ec7dd
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 9 deletions.
10 changes: 7 additions & 3 deletions examples/advanced_tensorflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ poetry run python3 -c "import flwr"

If you don't see any errors you're good to go!

# Run Federated Learning with TensorFlow/Keras and Flower
## Run Federated Learning with TensorFlow/Keras and Flower

The included `run.sh` will start the Flower server (using `server.py`), sleep for 2 seconds to ensure the the server is up, and then start 10 Flower clients (using `client.py`). You can simply start everything in a terminal as follows:
The included `run.sh` will call a script to generate certificates (which will be used by server and clients), start the Flower server (using `server.py`), sleep for 2 seconds to ensure the the server is up, and then start 10 Flower clients (using `client.py`). You can simply start everything in a terminal as follows:

```shell
poetry run ./run.sh
```

The `run.sh` script starts processes in the background so that you don't have to open eleven terminal windows. If you experiment with the code example and something goes wrong, simply using `CTRL + C` on Linux (or `CMD + C` on macOS) wouldn't normally kill all these processes, which is why the script ends with `trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT` and `wait`. This simply allows you to stop the experiment using `CTRL + C` (or `CMD + C`). If you change the script and anyhting goes wrong you can still use `killall python` (or `killall python3`) to kill all background processes (or a more specific command if you have other Python processes running that you don't want to kill).
The `run.sh` script starts processes in the background so that you don't have to open eleven terminal windows. If you experiment with the code example and something goes wrong, simply using `CTRL + C` on Linux (or `CMD + C` on macOS) wouldn't normally kill all these processes, which is why the script ends with `trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT` and `wait`. This simply allows you to stop the experiment using `CTRL + C` (or `CMD + C`). If you change the script and anything goes wrong you can still use `killall python` (or `killall python3`) to kill all background processes (or a more specific command if you have other Python processes running that you don't want to kill).

## Important / Warning

The approach used to generate SSL certificates can serve as an inspiration and starting point, but it should not be considered as viable for production environments. Please refer to other sources regarding the issue of correctly generating certificates for production environments.
20 changes: 20 additions & 0 deletions examples/advanced_tensorflow/certificates/certificate.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[req]
default_bits = 4096
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn

[dn]
C = DE
ST = HH
O = Flower
CN = localhost

[req_ext]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
IP.1 = ::1
IP.2 = 127.0.0.1
52 changes: 52 additions & 0 deletions examples/advanced_tensorflow/certificates/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash
# This script will generate all certificates if ca.crt does not exist

set -e
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/../

CA_PASSWORD=notsafe

CERT_DIR=.cache/certificates

# Generate directories if not exists
mkdir -p .cache/certificates

# if [ -f ".cache/certificates/ca.crt" ]; then
# echo "Skipping certificate generation as they already exist."
# exit 0
# fi

rm -f $CERT_DIR/*

# Generate the root certificate authority key and certificate based on key
openssl genrsa -out $CERT_DIR/ca.key 4096
openssl req \
-new \
-x509 \
-key $CERT_DIR/ca.key \
-sha256 \
-subj "/C=DE/ST=HH/O=CA, Inc." \
-days 365 -out $CERT_DIR/ca.crt

# Generate a new private key for the server
openssl genrsa -out $CERT_DIR/server.key 4096

# Create a signing CSR
openssl req \
-new \
-key $CERT_DIR/server.key \
-out $CERT_DIR/server.csr \
-config ./certificates/certificate.conf

# Generate a certificate for the server
openssl x509 \
-req \
-in $CERT_DIR/server.csr \
-CA $CERT_DIR/ca.crt \
-CAkey $CERT_DIR/ca.key \
-CAcreateserial \
-out $CERT_DIR/server.pem \
-days 365 \
-sha256 \
-extfile ./certificates/certificate.conf \
-extensions req_ext
13 changes: 11 additions & 2 deletions examples/advanced_tensorflow/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import argparse
import os
from pathlib import Path

import numpy as np
import tensorflow as tf

import flwr as fl
Expand All @@ -17,6 +17,10 @@ def __init__(self, model, x_train, y_train, x_test, y_test):
self.x_train, self.y_train = x_train, y_train
self.x_test, self.y_test = x_test, y_test

def get_properties(self, config):
"""Get properties of client."""
raise Exception("Not implemented")

def get_parameters(self):
"""Get parameters of the local model."""
raise Exception("Not implemented (server-side parameter initialization)")
Expand Down Expand Up @@ -83,7 +87,12 @@ def main() -> None:

# Start Flower client
client = CifarClient(model, x_train, y_train, x_test, y_test)
fl.client.start_numpy_client("[::]:8080", client=client)

fl.client.start_numpy_client(
server_address="localhost:8080",
client=client,
root_certificates=Path(".cache/certificates/ca.crt").read_bytes(),
)


def load_partition(idx: int):
Expand Down
6 changes: 6 additions & 0 deletions examples/advanced_tensorflow/run.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#!/bin/bash

./certificates/generate.sh

echo "Starting server"

python server.py &
sleep 3 # Sleep for 3s to give the server enough time to start

# Ensure that the Keras dataset used in client.py is already cached.
python -c "import tensorflow as tf; tf.keras.datasets.cifar10.load_data()"

for i in `seq 0 9`; do
echo "Starting client $i"
python client.py --partition=${i} &
Expand Down
12 changes: 9 additions & 3 deletions examples/advanced_tensorflow/server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Dict, Optional, Tuple
from pathlib import Path

import flwr as fl
import tensorflow as tf
Expand Down Expand Up @@ -26,11 +27,16 @@ def main() -> None:
initial_parameters=fl.common.weights_to_parameters(model.get_weights()),
)

# Start Flower server for four rounds of federated learning
# Start Flower server (SSL-enabled) for four rounds of federated learning
fl.server.start_server(
server_address="[::]:8080",
server_address="0.0.0.0:8080",
config={"num_rounds": 4},
strategy=strategy,
certificates=(
Path(".cache/certificates/ca.crt").read_bytes(),
Path(".cache/certificates/server.pem").read_bytes(),
Path(".cache/certificates/server.key").read_bytes(),
),
)


Expand Down
2 changes: 1 addition & 1 deletion examples/quickstart_tensorflow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ poetry run python3 -c "import flwr"

If you don't see any errors you're good to go!

# Run Federated Learning with TensorFlow/Keras and Flower
## Run Federated Learning with TensorFlow/Keras and Flower

Afterwards you are ready to start the Flower server as well as the clients. You can simply start the server in a terminal as follows:

Expand Down

0 comments on commit 68ec7dd

Please sign in to comment.