From 151ee1e0f980f4cce05eb63e40c7e3f23bfb6d78 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Sat, 24 Dec 2022 11:11:42 -0800 Subject: [PATCH 01/19] optional cert dir Signed-off-by: Mansi Sharma --- openfl/federated/plan/plan.py | 4 +-- openfl/interface/aggregator.py | 39 ++++++++++++++++++++++------ openfl/interface/collaborator.py | 44 ++++++++++++++++++++++++-------- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/openfl/federated/plan/plan.py b/openfl/federated/plan/plan.py index fbafb6a449..872e64ba21 100644 --- a/openfl/federated/plan/plan.py +++ b/openfl/federated/plan/plan.py @@ -500,7 +500,7 @@ def get_client(self, collaborator_name, aggregator_uuid, federation_uuid, """Get gRPC client for the specified collaborator.""" common_name = collaborator_name if not root_certificate or not private_key or not certificate: - root_certificate = 'cert/cert_chain.crt' + root_certificate = f'cert/cert_chain.crt' certificate = f'cert/client/col_{common_name}.crt' private_key = f'cert/client/col_{common_name}.key' @@ -525,7 +525,7 @@ def get_server(self, root_certificate=None, private_key=None, certificate=None, common_name = self.config['network'][SETTINGS]['agg_addr'].lower() if not root_certificate or not private_key or not certificate: - root_certificate = 'cert/cert_chain.crt' + root_certificate = f'cert/cert_chain.crt' certificate = f'cert/server/agg_{common_name}.crt' private_key = f'cert/server/agg_{common_name}.key' diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index a11122fcb6..1592caf247 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -36,7 +36,9 @@ def aggregator(context): default='plan/cols.yaml', type=ClickPath(exists=True)) @option('-s', '--secure', required=False, help='Enable Intel SGX Enclave', is_flag=True, default=False) -def start_(plan, authorized_cols, secure): +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) +def start_(plan, authorized_cols, secure, cert_path): """Start the aggregator service.""" from pathlib import Path @@ -54,7 +56,18 @@ def start_(plan, authorized_cols, secure): logger.info('🧿 Starting the Aggregator Service.') - plan.get_server().serve() + if cert_path: + CERT_DIR = Path(cert_path).absolute() + if not Path(CERT_DIR).exists(): + echo(style('Certificate Path not found.', fg='red') + + ' Please run `fx aggregator generate-cert-request --cert_path`' + ' to generate certs under this directory first.') + plan.get_server( + root_certificate=f'{CERT_DIR}/cert_chain.crt', + private_key=f'{CERT_DIR}/server/agg_{common_name}.key', + certificate=f'{CERT_DIR}/server/agg_{common_name}.crt').serve() + else: + plan.get_server().serve() @aggregator.command(name='generate-cert-request') @@ -62,12 +75,15 @@ def start_(plan, authorized_cols, secure): help=f'The fully qualified domain name of' f' aggregator node [{getfqdn_env()}]', default=getfqdn_env()) -def _generate_cert_request(fqdn): - generate_cert_request(fqdn) +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) +def _generate_cert_request(fqdn, cert_path): + generate_cert_request(fqdn, cert_path) -def generate_cert_request(fqdn): +def generate_cert_request(fqdn, cert_path=None): """Create aggregator certificate key pair.""" + from pathlib import Path from openfl.cryptography.participant import generate_csr from openfl.cryptography.io import write_crt from openfl.cryptography.io import write_key @@ -86,6 +102,8 @@ def generate_cert_request(fqdn): server_private_key, server_csr = generate_csr(common_name, server=True) + if cert_path: + CERT_DIR = Path(cert_path).absolute() (CERT_DIR / 'server').mkdir(parents=True, exist_ok=True) echo(' Writing AGGREGATOR certificate key pair to: ' + style( @@ -113,11 +131,13 @@ def find_certificate_name(file_name): help=f'The fully qualified domain name of aggregator node [{getfqdn_env()}]', default=getfqdn_env()) @option('-s', '--silent', help='Do not prompt', is_flag=True) -def _certify(fqdn, silent): - certify(fqdn, silent) +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) +def _certify(fqdn, silent, cert_path): + certify(fqdn, silent, cert_path) -def certify(fqdn, silent): +def certify(fqdn, silent, cert_path=None): """Sign/certify the aggregator certificate key pair.""" from pathlib import Path @@ -140,6 +160,9 @@ def certify(fqdn, silent): signing_crt_path = 'ca/signing-ca.crt' # Load CSR + if cert_path: + CERT_DIR = Path(cert_path).absolute() + csr_path_absolute_path = Path(CERT_DIR / f'{cert_name}.csr').absolute() if not csr_path_absolute_path.exists(): echo(style('Aggregator certificate signing request not found.', fg='red') diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 2c6e9b020b..d5dcb43d12 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -37,7 +37,9 @@ def collaborator(context): help='The certified common name of the collaborator') @option('-s', '--secure', required=False, help='Enable Intel SGX Enclave', is_flag=True, default=False) -def start_(plan, collaborator_name, data_config, secure): +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) +def start_(plan, collaborator_name, data_config, secure, cert_path): """Start a collaborator service.""" from pathlib import Path @@ -57,8 +59,20 @@ def start_(plan, collaborator_name, data_config, secure): echo(f'Data = {plan.cols_data_paths}') logger.info('🧿 Starting a Collaborator Service.') - - plan.get_collaborator(collaborator_name).run() + + if cert_path: + CERT_DIR = Path(cert_path).absolute() + if not Path(CERT_DIR).exists(): + echo(style('Certificate Path not found.', fg='red') + + ' Please run `fx collaborator generate-cert-request --cert_path`' + ' to generate certs under this directory first.') + plan.get_collaborator( + collaborator_name, + root_certificate=f'{CERT_DIR}/cert_chain.crt', + private_key=f'{CERT_DIR}/client/col_{common_name}.key', + certificate=f'{CERT_DIR}/client/col_{common_name}.crt').run() + else: + plan.get_collaborator(collaborator_name).run() def register_data_path(collaborator_name, data_path=None, silent=False): @@ -117,21 +131,24 @@ def register_data_path(collaborator_name, data_path=None, silent=False): @option('-x', '--skip-package', help='Do not package the certificate signing request for export', is_flag=True) +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) def generate_cert_request_(collaborator_name, - data_path, silent, skip_package): + data_path, silent, skip_package, cert_path): """Generate certificate request for the collaborator.""" if data_path and is_directory_traversal(data_path): echo('Data path is out of the openfl workspace scope.') sys.exit(1) - generate_cert_request(collaborator_name, data_path, silent, skip_package) + generate_cert_request(collaborator_name, data_path, silent, skip_package, cert_path) -def generate_cert_request(collaborator_name, data_path, silent, skip_package): +def generate_cert_request(collaborator_name, data_path, silent, skip_package, cert_path=None): """ Create collaborator certificate key pair. Then create a package with the CSR to send for signing. """ + from pathlib import Path from openfl.cryptography.participant import generate_csr from openfl.cryptography.io import write_crt from openfl.cryptography.io import write_key @@ -146,7 +163,9 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package): f' SAN={style(subject_alternative_name, fg="red")}') client_private_key, client_csr = generate_csr(common_name, server=False) - + + if cert_path: + CERT_DIR = Path(cert_path).absolute() (CERT_DIR / 'client').mkdir(parents=True, exist_ok=True) echo(' Moving COLLABORATOR certificate to: ' + style( @@ -258,12 +277,14 @@ def register_collaborator(file_name): @option('-i', '--import', 'import_', type=ClickPath(exists=True), help='Import the archive containing the collaborator\'s' ' certificate (signed by the CA)') -def certify_(collaborator_name, silent, request_pkg, import_): +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) +def certify_(collaborator_name, silent, request_pkg, import_, cert_path): """Certify the collaborator.""" - certify(collaborator_name, silent, request_pkg, import_) + certify(collaborator_name, silent, request_pkg, import_, cert_path) -def certify(collaborator_name, silent, request_pkg=None, import_=False): +def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_path=None): """Sign/certify collaborator certificate key pair.""" from click import confirm from pathlib import Path @@ -285,6 +306,9 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False): common_name = f'{collaborator_name}'.lower() + if cert_path: + CERT_DIR = Path(cert_path).absolute() + if not import_: if request_pkg: Path(f'{CERT_DIR}/client').mkdir(parents=True, exist_ok=True) From e21533ccca1d419937baeb3f9464861d471eac4a Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Tue, 27 Dec 2022 02:45:15 -0800 Subject: [PATCH 02/19] adding cert path option Signed-off-by: Mansi Sharma --- openfl/interface/aggregator.py | 9 +- openfl/interface/collaborator.py | 1 + openfl/interface/workspace.py | 11 ++- tests/github/test_pki_cert_location.sh | 131 +++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 4 deletions(-) create mode 100755 tests/github/test_pki_cert_location.sh diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 1592caf247..6e5e85c144 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -38,7 +38,11 @@ def aggregator(context): help='Enable Intel SGX Enclave', is_flag=True, default=False) @option('-c', '--cert_path', help='The cert path where pki certs will reside', required=False) -def start_(plan, authorized_cols, secure, cert_path): +@option('--fqdn', required=False, type=click_types.FQDN, + help=f'The fully qualified domain name of' + f' aggregator node [{getfqdn_env()}]', + default=getfqdn_env()) +def start_(plan, authorized_cols, secure, cert_path, fqdn): """Start the aggregator service.""" from pathlib import Path @@ -62,6 +66,9 @@ def start_(plan, authorized_cols, secure, cert_path): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx aggregator generate-cert-request --cert_path`' ' to generate certs under this directory first.') + if fqdn is None: + fqdn = getfqdn_env() + common_name = f'{fqdn}'.lower() plan.get_server( root_certificate=f'{CERT_DIR}/cert_chain.crt', private_key=f'{CERT_DIR}/server/agg_{common_name}.key', diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index d5dcb43d12..829a0b2ed5 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -66,6 +66,7 @@ def start_(plan, collaborator_name, data_config, secure, cert_path): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx collaborator generate-cert-request --cert_path`' ' to generate certs under this directory first.') + common_name = f'{collaborator_name}'.lower() plan.get_collaborator( collaborator_name, root_certificate=f'{CERT_DIR}/cert_chain.crt', diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index 09cd18bf7c..20fe322d36 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -213,12 +213,14 @@ def import_(archive): @workspace.command(name='certify') -def certify_(): +@option('-c', '--cert_path', + help='The cert path where pki certs will reside', required=False) +def certify_(cert_path): """Create certificate authority for federation.""" - certify() + certify(cert_path) -def certify(): +def certify(cert_path=None): """Create certificate authority for federation.""" from cryptography.hazmat.primitives import serialization @@ -232,6 +234,9 @@ def certify(): echo('1. Create Root CA') echo('1.1 Create Directories') + if cert_path: + CERT_DIR = Path(cert_path).absolute() + (CERT_DIR / 'ca/root-ca/private').mkdir( parents=True, exist_ok=True, mode=0o700) (CERT_DIR / 'ca/root-ca/db').mkdir(parents=True, exist_ok=True) diff --git a/tests/github/test_pki_cert_location.sh b/tests/github/test_pki_cert_location.sh new file mode 100755 index 0000000000..c4c96a19d2 --- /dev/null +++ b/tests/github/test_pki_cert_location.sh @@ -0,0 +1,131 @@ +set -e +# Test the pipeline + +TEMPLATE=${1:-'keras_cnn_mnist'} # ['torch_cnn_mnist', 'keras_cnn_mnist'] +FED_WORKSPACE=${2:-'fed_work12345alpha81671'} # This can be whatever unique directory name you want +COL1=${3:-'one123dragons'} # This can be any unique label (lowercase) +COL2=${4:-'beta34unicorns'} # This can be any unique label (lowercase) + +FQDN=${5:-$(hostname --all-fqdns | awk '{print $1}')} +CERT_PATH="${HOME}/.openfl" + +COL1_DATA_PATH=1 +COL2_DATA_PATH=2 + +help() { + echo "Usage: test_hello_federation.sh TEMPLATE FED_WORKSPACE COL1 COL2 [OPTIONS]" + echo + echo "Options:" + echo "--rounds-to-train rounds to train" + echo "--col1-data-path data path for collaborator 1" + echo "--col2-data-path data path for collaborator 2" + echo "--save-model path to save model in native format" + echo "-h, --help display this help and exit" +} + +# Getting additional options +ADD_OPTS=$(getopt -o "h" -l "rounds-to-train:,col1-data-path:, +col2-data-path:,save-model:,help" -n test_hello_federation.sh -- "$@") +eval set -- "$ADD_OPTS" +while (($#)); do + case "${1:-}" in + (--rounds-to-train) ROUNDS_TO_TRAIN="$2" ; shift 2 ;; + (--col1-data-path) COL1_DATA_PATH="$2" ; shift 2 ;; + (--col2-data-path) COL2_DATA_PATH="$2" ; shift 2 ;; + (--save-model) SAVE_MODEL="$2" ; shift 2 ;; + (-h|--help) help ; exit 0 ;; + + (--) shift ; break ;; + (*) echo "Invalid option: ${1:-}"; exit 1 ;; + esac +done + + + +create_collaborator() { + + FED_WORKSPACE=$1 + FED_DIRECTORY=$2 + COL=$3 + COL_DIRECTORY=$4 + DATA_PATH=$5 + + ARCHIVE_NAME="${FED_WORKSPACE}.zip" + + # Copy workspace to collaborator directories (these can be on different machines) + rm -rf ${COL_DIRECTORY} # Remove any existing directory + mkdir -p ${COL_DIRECTORY} # Create a new directory for the collaborator + cd ${COL_DIRECTORY} + fx workspace import --archive ${FED_DIRECTORY}/${ARCHIVE_NAME} # Import the workspace to this collaborator + + # Create collaborator certificate request + cd ${COL_DIRECTORY}/${FED_WORKSPACE} + fx collaborator generate-cert-request -d ${DATA_PATH} -n ${COL} -c ${CERT_PATH} --silent # Remove '--silent' if you run this manually + + # Sign collaborator certificate + cd ${FED_DIRECTORY} # Move back to the Aggregator + fx collaborator certify --request-pkg ${COL_DIRECTORY}/${FED_WORKSPACE}/col_${COL}_to_agg_cert_request.zip -c ${CERT_PATH} --silent # Remove '--silent' if you run this manually + + #Import the signed certificate from the aggregator + cd ${COL_DIRECTORY}/${FED_WORKSPACE} + fx collaborator certify --import ${FED_DIRECTORY}/agg_to_col_${COL}_signed_cert.zip -c ${CERT_PATH} + +} + +# START +# ===== +# Make sure you are in a Python virtual environment with the FL package installed. + +# Create FL workspace +rm -rf ${FED_WORKSPACE} +fx workspace create --prefix ${FED_WORKSPACE} --template ${TEMPLATE} +cd ${FED_WORKSPACE} +FED_DIRECTORY=`pwd` # Get the absolute directory path for the workspace + +# Initialize FL plan +fx plan initialize -a ${FQDN} + +# Set rounds to train if given +if [[ ! -z "$ROUNDS_TO_TRAIN" ]] +then + sed -i "/rounds_to_train/c\ rounds_to_train: $ROUNDS_TO_TRAIN" plan/plan.yaml +fi + +# Create certificate authority for workspace +fx workspace certify -c ${CERT_PATH} + +# Export FL workspace +fx workspace export + +# Create aggregator certificate +fx aggregator generate-cert-request --fqdn ${FQDN} -c ${CERT_PATH} + +# Sign aggregator certificate +fx aggregator certify --fqdn ${FQDN} -c ${CERT_PATH} --silent # Remove '--silent' if you run this manually + +# Create collaborator #1 +COL1_DIRECTORY=${FED_DIRECTORY}/${COL1} +create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL1} ${COL1_DIRECTORY} ${COL1_DATA_PATH} + +# Create collaborator #2 +COL2_DIRECTORY=${FED_DIRECTORY}/${COL2} +create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL2} ${COL2_DIRECTORY} ${COL2_DATA_PATH} + +# # Run the federation +cd ${FED_DIRECTORY} +fx aggregator start -c ${CERT_PATH} --fqdn ${FQDN} & +sleep 5 +cd ${COL1_DIRECTORY}/${FED_WORKSPACE} +fx collaborator start -n ${COL1} -c ${CERT_PATH} & +cd ${COL2_DIRECTORY}/${FED_WORKSPACE} +fx collaborator start -n ${COL2} -c ${CERT_PATH} +wait + +# # Convert model to native format +if [[ ! -z "$SAVE_MODEL" ]] +then + cd ${FED_DIRECTORY} + fx model save -i "./save/${TEMPLATE}_last.pbuf" -o ${SAVE_MODEL} +fi + +rm -rf ${FED_DIRECTORY} From e2c1a56314c5a1c4c31e4f0a5916ddd543da1452 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Tue, 27 Dec 2022 09:11:17 -0800 Subject: [PATCH 03/19] add test Signed-off-by: Mansi Sharma --- .github/workflows/pki.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pki.yml b/.github/workflows/pki.yml index e78014d9c0..9f4334dee7 100644 --- a/.github/workflows/pki.yml +++ b/.github/workflows/pki.yml @@ -46,4 +46,22 @@ jobs: pip install . - name: Test PKI run: | - python tests/github/pki_wrong_cn.py \ No newline at end of file + python tests/github/pki_wrong_cn.py + + test_pki_cert_location: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v3 + with: + python-version: "3.8" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + - name: Test PKI + run: | + bash tests/github/test_pki_cert_location.sh torch_cnn_mnist aggregator col1 col2 $(hostname --all-fqdns | awk '{print $1}') --rounds-to-train 3 \ No newline at end of file From 8e208a3a56d4172c7f9e657b907d95a1050ae967 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Wed, 28 Dec 2022 10:04:08 -0800 Subject: [PATCH 04/19] fix lint errors Signed-off-by: Mansi Sharma --- openfl/federated/plan/plan.py | 4 ++-- openfl/interface/aggregator.py | 11 +++++------ openfl/interface/collaborator.py | 17 ++++++++--------- openfl/interface/workspace.py | 4 ++-- tests/github/test_pki_cert_location.sh | 2 +- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/openfl/federated/plan/plan.py b/openfl/federated/plan/plan.py index 872e64ba21..fbafb6a449 100644 --- a/openfl/federated/plan/plan.py +++ b/openfl/federated/plan/plan.py @@ -500,7 +500,7 @@ def get_client(self, collaborator_name, aggregator_uuid, federation_uuid, """Get gRPC client for the specified collaborator.""" common_name = collaborator_name if not root_certificate or not private_key or not certificate: - root_certificate = f'cert/cert_chain.crt' + root_certificate = 'cert/cert_chain.crt' certificate = f'cert/client/col_{common_name}.crt' private_key = f'cert/client/col_{common_name}.key' @@ -525,7 +525,7 @@ def get_server(self, root_certificate=None, private_key=None, certificate=None, common_name = self.config['network'][SETTINGS]['agg_addr'].lower() if not root_certificate or not private_key or not certificate: - root_certificate = f'cert/cert_chain.crt' + root_certificate = 'cert/cert_chain.crt' certificate = f'cert/server/agg_{common_name}.crt' private_key = f'cert/server/agg_{common_name}.key' diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 6e5e85c144..4dc2c78c7f 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -69,10 +69,9 @@ def start_(plan, authorized_cols, secure, cert_path, fqdn): if fqdn is None: fqdn = getfqdn_env() common_name = f'{fqdn}'.lower() - plan.get_server( - root_certificate=f'{CERT_DIR}/cert_chain.crt', - private_key=f'{CERT_DIR}/server/agg_{common_name}.key', - certificate=f'{CERT_DIR}/server/agg_{common_name}.crt').serve() + plan.get_server(root_certificate=f'{CERT_DIR}/cert_chain.crt', + private_key=f'{CERT_DIR}/server/agg_{common_name}.key', + certificate=f'{CERT_DIR}/server/agg_{common_name}.crt').serve() else: plan.get_server().serve() @@ -110,7 +109,7 @@ def generate_cert_request(fqdn, cert_path=None): server_private_key, server_csr = generate_csr(common_name, server=True) if cert_path: - CERT_DIR = Path(cert_path).absolute() + CERT_DIR = Path(cert_path).absolute() # NOQA (CERT_DIR / 'server').mkdir(parents=True, exist_ok=True) echo(' Writing AGGREGATOR certificate key pair to: ' + style( @@ -168,7 +167,7 @@ def certify(fqdn, silent, cert_path=None): # Load CSR if cert_path: - CERT_DIR = Path(cert_path).absolute() + CERT_DIR = Path(cert_path).absolute() # NOQA csr_path_absolute_path = Path(CERT_DIR / f'{cert_name}.csr').absolute() if not csr_path_absolute_path.exists(): diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 829a0b2ed5..792d152142 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -59,7 +59,7 @@ def start_(plan, collaborator_name, data_config, secure, cert_path): echo(f'Data = {plan.cols_data_paths}') logger.info('🧿 Starting a Collaborator Service.') - + if cert_path: CERT_DIR = Path(cert_path).absolute() if not Path(CERT_DIR).exists(): @@ -67,11 +67,10 @@ def start_(plan, collaborator_name, data_config, secure, cert_path): + ' Please run `fx collaborator generate-cert-request --cert_path`' ' to generate certs under this directory first.') common_name = f'{collaborator_name}'.lower() - plan.get_collaborator( - collaborator_name, - root_certificate=f'{CERT_DIR}/cert_chain.crt', - private_key=f'{CERT_DIR}/client/col_{common_name}.key', - certificate=f'{CERT_DIR}/client/col_{common_name}.crt').run() + plan.get_collaborator(collaborator_name, + root_certificate=f'{CERT_DIR}/cert_chain.crt', + private_key=f'{CERT_DIR}/client/col_{common_name}.key', + certificate=f'{CERT_DIR}/client/col_{common_name}.crt').run() else: plan.get_collaborator(collaborator_name).run() @@ -164,9 +163,9 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, ce f' SAN={style(subject_alternative_name, fg="red")}') client_private_key, client_csr = generate_csr(common_name, server=False) - + if cert_path: - CERT_DIR = Path(cert_path).absolute() + CERT_DIR = Path(cert_path).absolute() # NOQA (CERT_DIR / 'client').mkdir(parents=True, exist_ok=True) echo(' Moving COLLABORATOR certificate to: ' + style( @@ -308,7 +307,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat common_name = f'{collaborator_name}'.lower() if cert_path: - CERT_DIR = Path(cert_path).absolute() + CERT_DIR = Path(cert_path).absolute() # NOQA if not import_: if request_pkg: diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index 20fe322d36..41ec553cea 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -235,8 +235,8 @@ def certify(cert_path=None): echo('1.1 Create Directories') if cert_path: - CERT_DIR = Path(cert_path).absolute() - + CERT_DIR = Path(cert_path).absolute() # NOQA + (CERT_DIR / 'ca/root-ca/private').mkdir( parents=True, exist_ok=True, mode=0o700) (CERT_DIR / 'ca/root-ca/db').mkdir(parents=True, exist_ok=True) diff --git a/tests/github/test_pki_cert_location.sh b/tests/github/test_pki_cert_location.sh index c4c96a19d2..e5c8ed45c8 100755 --- a/tests/github/test_pki_cert_location.sh +++ b/tests/github/test_pki_cert_location.sh @@ -7,7 +7,7 @@ COL1=${3:-'one123dragons'} # This can be any unique label (lowercase) COL2=${4:-'beta34unicorns'} # This can be any unique label (lowercase) FQDN=${5:-$(hostname --all-fqdns | awk '{print $1}')} -CERT_PATH="${HOME}/.openfl" +CERT_PATH=${6:-"${HOME}/.openfl"} COL1_DATA_PATH=1 COL2_DATA_PATH=2 From f84ae1462a9df143de0751cca667d07a9f84af4d Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Mon, 2 Jan 2023 10:36:04 -0800 Subject: [PATCH 05/19] update cert dir Signed-off-by: Mansi Sharma --- docs/running_the_federation.rst | 69 +++++++++++++++++++++++--- openfl/interface/aggregator.py | 28 +++++++++-- openfl/interface/collaborator.py | 31 ++++++++++-- openfl/interface/workspace.py | 20 +++++++- tests/github/test_pki_cert_location.sh | 37 ++++++++++---- 5 files changed, 160 insertions(+), 25 deletions(-) diff --git a/docs/running_the_federation.rst b/docs/running_the_federation.rst index d507126fc0..cb40641482 100644 --- a/docs/running_the_federation.rst +++ b/docs/running_the_federation.rst @@ -952,12 +952,26 @@ Setting Up the Certificate Authority fx workspace certify + By default, all certificates are stored under :code:`WORKSPACE_PATH/cert` folder inside workspace. To store certificates elsewhere: + + .. code-block:: console + + fx workspace certify -c CERT_PATH + + where :code:`CERT_PATH` is the path where the certificates will be stored for this node. + 3. Run the aggregator certificate creation command, replacing :code:`AFQDN` with the actual `fully qualified domain name (FQDN) `_ for the aggregator node. .. code-block:: console fx aggregator generate-cert-request --fqdn AFQDN + To store certificates under :code:`CERT_PATH`: + + .. code-block:: console + + fx aggregator generate-cert-request --fqdn AFQDN -c CERT_PATH + .. note:: On Linux\*\, you can discover the FQDN with this command: @@ -986,6 +1000,11 @@ Setting Up the Certificate Authority fx aggregator certify --fqdn AFQDN + If :code:`CERT_PATH` was used to store CA signing certificates, specify the same path here: + + .. code-block:: console + + fx aggregator certify --fqdn AFQDN -c CERT_PATH .. note:: @@ -1000,14 +1019,14 @@ Setting Up the Certificate Authority +---------------------------+--------------------------------------------------+ | File Type | Filename | +===========================+==================================================+ - | Certificate chain | WORKSPACE.PATH/cert/cert_chain.crt | + | Certificate chain | CERT.PATH/cert/cert_chain.crt | +---------------------------+--------------------------------------------------+ - | Aggregator certificate | WORKSPACE.PATH/cert/server/agg_{AFQDN}.crt | + | Aggregator certificate | CERT.PATH/cert/server/agg_{AFQDN}.crt | +---------------------------+--------------------------------------------------+ - | Aggregator key | WORKSPACE.PATH/cert/server/agg_{AFQDN}.key | + | Aggregator key | CERT.PATH/cert/server/agg_{AFQDN}.key | +---------------------------+--------------------------------------------------+ - where **AFQDN** is the fully-qualified domain name of the aggregator node. + where **CERT.PATH** is :code:`WORKSPACE.PATH` by default or the path specified by the user and **AFQDN** is the fully-qualified domain name of the aggregator node. .. _workspace_export: @@ -1049,6 +1068,13 @@ Importing the Workspace fx collaborator generate-cert-request -n {COL_LABEL} + To store certs under :code:`CERT_PATH_COL/cert` other than :code:`WORKSPACE_PATH/cert`: + + .. code-block:: console + + fx collaborator generate-cert-request -n {COL_LABEL} -c {CERT_PATH_COL} + + where **CERT_PATH_COL** is the path where collaborator certificates (client) will be stored. The creation script will also ask you to specify the path to the data. For this example, enter the integer that represents which MNIST shard to use on this collaborator node. For the first collaborator node enter **1**. For the second collaborator node enter **2**. @@ -1057,13 +1083,14 @@ Importing the Workspace +-----------------------------+--------------------------------------------------------+ | File Type | Filename | +=============================+========================================================+ - | Collaborator CSR | WORKSPACE.PATH/cert/client/col_{COL_LABEL}.csr | + | Collaborator CSR | CERT.PATH.COL/cert/client/col_{COL_LABEL}.csr | +-----------------------------+--------------------------------------------------------+ - | Collaborator key | WORKSPACE.PATH/cert/client/col_{COL_LABEL}.key | + | Collaborator key | CERT.PATH.COL/cert/client/col_{COL_LABEL}.key | +-----------------------------+--------------------------------------------------------+ | Collaborator CSR Package | WORKSPACE.PATH/col_{COL_LABEL}_to_agg_cert_request.zip | +-----------------------------+--------------------------------------------------------+ + where **CERT.PATH.COL** is :code:`WORKSPACE.PATH` by default or the path specified by the user. 4. On the aggregator node (i.e., the certificate authority in this example), sign the Collaborator CSR Package from the collaborator nodes. @@ -1073,6 +1100,12 @@ Importing the Workspace where :code:`/PATH/TO/col_{COL_LABEL}_to_agg_cert_request.zip` is the path to the Collaborator CSR Package containing the :code:`.csr` file from the collaborator node. The certificate authority will sign this certificate for use in the federation. + If :code:`CERT_PATH` was used at the aggregator node to store CA signing certificates, specify the same path here: + + .. code-block:: console + + fx collaborator certify --request-pkg /PATH/TO/col_{COL_LABEL}_to_agg_cert_request.zip -c CERT_PATH + The command packages the signed collaborator certificate, along with the **cert_chain.crt** file needed to verify certificate signatures, for transport back to the collaborator node: +---------------------------------+------------------------------------------------------------+ @@ -1087,6 +1120,11 @@ Importing the Workspace fx collaborator certify --import /PATH/TO/agg_to_col_{COL_LABEL}_signed_cert.zip + If :code:`CERT_PATH_COL` was used to store collaborator certificates for this node, specify the collaborator certificate path here: + + .. code-block:: console + + fx collaborator certify --import /PATH/TO/agg_to_col_{COL_LABEL}_signed_cert.zip -c CERT_PATH_COL .. _running_the_federation.start_nodes: @@ -1103,6 +1141,12 @@ STEP 3: Start the Federation fx aggregator start + If :code:`CERT_PATH` was used to store certificates for this node, specify the same path here: + + .. code-block:: console + + fx aggregator start -c ${CERT_PATH} + Now, the Aggregator is running and waiting for Collaborators to connect. .. _running_collaborators: @@ -1119,6 +1163,12 @@ STEP 3: Start the Federation where :code:`COLLABORATOR_LABEL` is the label for this Collaborator. + If :code:`CERT_PATH_COL` was used to store certificates for this node, specify the same path here: + + .. code-block:: console + + fx collaborator start -n {COLLABORATOR_LABEL} -c ${CERT_PATH_COL} + .. note:: Each workspace may have multiple FL plans and multiple collaborator lists associated with it. @@ -1160,6 +1210,13 @@ Another way to access the trained model is by calling the API command directly f In fact, the :code:`get_model()` method returns a **TaskRunner** object loaded with the chosen model snapshot. Users may utilize the linked model as a regular Python object. +If :code:`CERT_PATH` was used to store certificates for any node, uninstall them: + +.. code-block:: console + + fx workspace uninstall-cert -c ${CERT_PATH} + fx aggregator uninstall-cert -c ${CERT_PATH} + fx collaborator uninstall-cert -c ${CERT_PATH_COL} .. _running_the_federation_docker: diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 4dc2c78c7f..b22bb61d3f 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -61,7 +61,9 @@ def start_(plan, authorized_cols, secure, cert_path, fqdn): logger.info('🧿 Starting the Aggregator Service.') if cert_path: - CERT_DIR = Path(cert_path).absolute() + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH / 'cert' if not Path(CERT_DIR).exists(): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx aggregator generate-cert-request --cert_path`' @@ -109,7 +111,9 @@ def generate_cert_request(fqdn, cert_path=None): server_private_key, server_csr = generate_csr(common_name, server=True) if cert_path: - CERT_DIR = Path(cert_path).absolute() # NOQA + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH/ 'cert' # NOQA (CERT_DIR / 'server').mkdir(parents=True, exist_ok=True) echo(' Writing AGGREGATOR certificate key pair to: ' + style( @@ -167,7 +171,9 @@ def certify(fqdn, silent, cert_path=None): # Load CSR if cert_path: - CERT_DIR = Path(cert_path).absolute() # NOQA + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH/ 'cert' # NOQA csr_path_absolute_path = Path(CERT_DIR / f'{cert_name}.csr').absolute() if not csr_path_absolute_path.exists(): @@ -220,3 +226,19 @@ def certify(fqdn, silent, cert_path=None): echo(style('Not signing certificate.', fg='red') + ' Please check with this AGGREGATOR to get the correct' ' certificate for this federation.') + + +@aggregator.command(name='uninstall-cert') +@option('-c', '--cert_path', + help='The cert path where pki certs reside', required=True) +def _uninstall_cert(cert_path): + uninstall_cert(cert_path) + + +def uninstall_cert(cert_path=None): + """Uninstall certs under a given directory.""" + import shutil + from pathlib import Path + + cert_path = Path(cert_path).absolute() + shutil.rmtree(cert_path, ignore_errors=True) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 792d152142..1d2b42bd51 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -61,7 +61,9 @@ def start_(plan, collaborator_name, data_config, secure, cert_path): logger.info('🧿 Starting a Collaborator Service.') if cert_path: - CERT_DIR = Path(cert_path).absolute() + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH / 'cert' if not Path(CERT_DIR).exists(): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx collaborator generate-cert-request --cert_path`' @@ -165,11 +167,14 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, ce client_private_key, client_csr = generate_csr(common_name, server=False) if cert_path: - CERT_DIR = Path(cert_path).absolute() # NOQA + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH/ 'cert' # NOQA + (CERT_DIR / 'client').mkdir(parents=True, exist_ok=True) echo(' Moving COLLABORATOR certificate to: ' + style( - f'{CERT_DIR}/{file_name}', fg='green')) + f'{CERT_DIR}', fg='green')) # Write collaborator csr and key to disk write_crt(client_csr, CERT_DIR / 'client' / f'{file_name}.csr') @@ -307,7 +312,9 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat common_name = f'{collaborator_name}'.lower() if cert_path: - CERT_DIR = Path(cert_path).absolute() # NOQA + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH/ 'cert' # NOQA if not import_: if request_pkg: @@ -414,3 +421,19 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat echo(f'Certificate {crt} installed to PKI directory') else: echo('Certificate updated in the PKI directory') + + +@collaborator.command(name='uninstall-cert') +@option('-c', '--cert_path', + help='The cert path where pki certs reside', required=True) +def _uninstall_cert(cert_path): + uninstall_cert(cert_path) + + +def uninstall_cert(cert_path=None): + """Uninstall certs under a given directory.""" + import shutil + from pathlib import Path + + cert_path = Path(cert_path).absolute() + shutil.rmtree(cert_path, ignore_errors=True) diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index 41ec553cea..81e95b1da6 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -235,7 +235,9 @@ def certify(cert_path=None): echo('1.1 Create Directories') if cert_path: - CERT_DIR = Path(cert_path).absolute() # NOQA + CERT_PATH = Path(cert_path).absolute() + (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) + CERT_DIR = CERT_PATH/ 'cert' # NOQA (CERT_DIR / 'ca/root-ca/private').mkdir( parents=True, exist_ok=True, mode=0o700) @@ -531,6 +533,22 @@ def open_pipe(command: str): echo(f'\n ✔️ The image saved to file: {workspace_name}.tar.gz') +@workspace.command(name='uninstall-cert') +@option('-c', '--cert_path', + help='The cert path where pki certs reside', required=True) +def _uninstall_cert(cert_path): + uninstall_cert(cert_path) + + +def uninstall_cert(cert_path=None): + """Uninstall certs under a given directory.""" + import shutil + from pathlib import Path + + cert_path = Path(cert_path).absolute() + shutil.rmtree(cert_path, ignore_errors=True) + + def apply_template_plan(prefix, template): """Copy plan file from template folder. diff --git a/tests/github/test_pki_cert_location.sh b/tests/github/test_pki_cert_location.sh index e5c8ed45c8..1161834e9d 100755 --- a/tests/github/test_pki_cert_location.sh +++ b/tests/github/test_pki_cert_location.sh @@ -7,7 +7,10 @@ COL1=${3:-'one123dragons'} # This can be any unique label (lowercase) COL2=${4:-'beta34unicorns'} # This can be any unique label (lowercase) FQDN=${5:-$(hostname --all-fqdns | awk '{print $1}')} -CERT_PATH=${6:-"${HOME}/.openfl"} + +CERT_PATH_AG=${6:-"${HOME}/.openfl/aggregator"} +CERT_PATH_COL1=${7:-"${HOME}/.openfl/one123dragons"} +CERT_PATH_COL2=${8:-"${HOME}/.openfl/beta34unicorns"} COL1_DATA_PATH=1 COL2_DATA_PATH=2 @@ -49,6 +52,8 @@ create_collaborator() { COL=$3 COL_DIRECTORY=$4 DATA_PATH=$5 + CERT_PATH_COL=$6 + CERT_PATH=$7 ARCHIVE_NAME="${FED_WORKSPACE}.zip" @@ -60,7 +65,7 @@ create_collaborator() { # Create collaborator certificate request cd ${COL_DIRECTORY}/${FED_WORKSPACE} - fx collaborator generate-cert-request -d ${DATA_PATH} -n ${COL} -c ${CERT_PATH} --silent # Remove '--silent' if you run this manually + fx collaborator generate-cert-request -d ${DATA_PATH} -n ${COL} -c ${CERT_PATH_COL} --silent # Remove '--silent' if you run this manually # Sign collaborator certificate cd ${FED_DIRECTORY} # Move back to the Aggregator @@ -68,7 +73,7 @@ create_collaborator() { #Import the signed certificate from the aggregator cd ${COL_DIRECTORY}/${FED_WORKSPACE} - fx collaborator certify --import ${FED_DIRECTORY}/agg_to_col_${COL}_signed_cert.zip -c ${CERT_PATH} + fx collaborator certify --import ${FED_DIRECTORY}/agg_to_col_${COL}_signed_cert.zip -c ${CERT_PATH_COL} } @@ -92,33 +97,33 @@ then fi # Create certificate authority for workspace -fx workspace certify -c ${CERT_PATH} +fx workspace certify -c ${CERT_PATH_AG} # Export FL workspace fx workspace export # Create aggregator certificate -fx aggregator generate-cert-request --fqdn ${FQDN} -c ${CERT_PATH} +fx aggregator generate-cert-request --fqdn ${FQDN} -c ${CERT_PATH_AG} # Sign aggregator certificate -fx aggregator certify --fqdn ${FQDN} -c ${CERT_PATH} --silent # Remove '--silent' if you run this manually +fx aggregator certify --fqdn ${FQDN} -c ${CERT_PATH_AG} --silent # Remove '--silent' if you run this manually # Create collaborator #1 COL1_DIRECTORY=${FED_DIRECTORY}/${COL1} -create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL1} ${COL1_DIRECTORY} ${COL1_DATA_PATH} +create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL1} ${COL1_DIRECTORY} ${COL1_DATA_PATH} ${CERT_PATH_COL1} ${CERT_PATH_AG} # Create collaborator #2 COL2_DIRECTORY=${FED_DIRECTORY}/${COL2} -create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL2} ${COL2_DIRECTORY} ${COL2_DATA_PATH} +create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL2} ${COL2_DIRECTORY} ${COL2_DATA_PATH} ${CERT_PATH_COL2} ${CERT_PATH_AG} # # Run the federation cd ${FED_DIRECTORY} -fx aggregator start -c ${CERT_PATH} --fqdn ${FQDN} & +fx aggregator start -c ${CERT_PATH_AG} --fqdn ${FQDN} & sleep 5 cd ${COL1_DIRECTORY}/${FED_WORKSPACE} -fx collaborator start -n ${COL1} -c ${CERT_PATH} & +fx collaborator start -n ${COL1} -c ${CERT_PATH_COL1} & cd ${COL2_DIRECTORY}/${FED_WORKSPACE} -fx collaborator start -n ${COL2} -c ${CERT_PATH} +fx collaborator start -n ${COL2} -c ${CERT_PATH_COL2} wait # # Convert model to native format @@ -128,4 +133,14 @@ then fx model save -i "./save/${TEMPLATE}_last.pbuf" -o ${SAVE_MODEL} fi +# Clear cert directories + +cd ${FED_DIRECTORY} +fx aggregator uninstall-cert -c ${CERT_PATH_AG} +cd ${COL1_DIRECTORY}/${FED_WORKSPACE} +fx collaborator uninstall-cert -c ${CERT_PATH_COL1} +cd ${COL2_DIRECTORY}/${FED_WORKSPACE} +fx collaborator uninstall-cert -c ${CERT_PATH_COL2} + rm -rf ${FED_DIRECTORY} + From 241575e7983a4dbeb2bd0190d501738cd3cc98de Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Wed, 4 Jan 2023 04:26:49 -0800 Subject: [PATCH 06/19] address review comments Signed-off-by: Mansi Sharma --- .github/workflows/pki.yml | 17 --- openfl/interface/aggregator.py | 60 +++++----- openfl/interface/collaborator.py | 72 ++++++------ openfl/interface/workspace.py | 48 ++++---- tests/github/test_pki_cert_location.sh | 146 ------------------------- 5 files changed, 93 insertions(+), 250 deletions(-) delete mode 100755 tests/github/test_pki_cert_location.sh diff --git a/.github/workflows/pki.yml b/.github/workflows/pki.yml index 9f4334dee7..2bea96efe7 100644 --- a/.github/workflows/pki.yml +++ b/.github/workflows/pki.yml @@ -48,20 +48,3 @@ jobs: run: | python tests/github/pki_wrong_cn.py - test_pki_cert_location: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.8 - uses: actions/setup-python@v3 - with: - python-version: "3.8" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install . - - name: Test PKI - run: | - bash tests/github/test_pki_cert_location.sh torch_cnn_mnist aggregator col1 col2 $(hostname --all-fqdns | awk '{print $1}') --rounds-to-train 3 \ No newline at end of file diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index b22bb61d3f..618adae255 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -31,18 +31,14 @@ def aggregator(context): help='Federated learning plan [plan/plan.yaml]', default='plan/plan.yaml', type=ClickPath(exists=True)) -@option('-c', '--authorized_cols', required=False, +@option('-col', '--authorized_cols', required=False, help='Authorized collaborator list [plan/cols.yaml]', default='plan/cols.yaml', type=ClickPath(exists=True)) @option('-s', '--secure', required=False, help='Enable Intel SGX Enclave', is_flag=True, default=False) @option('-c', '--cert_path', help='The cert path where pki certs will reside', required=False) -@option('--fqdn', required=False, type=click_types.FQDN, - help=f'The fully qualified domain name of' - f' aggregator node [{getfqdn_env()}]', - default=getfqdn_env()) -def start_(plan, authorized_cols, secure, cert_path, fqdn): +def start_(plan, authorized_cols, secure, cert_path): """Start the aggregator service.""" from pathlib import Path @@ -61,19 +57,18 @@ def start_(plan, authorized_cols, secure, cert_path, fqdn): logger.info('🧿 Starting the Aggregator Service.') if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH / 'cert' - if not Path(CERT_DIR).exists(): + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + if not Path(cert_dir_path).exists(): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx aggregator generate-cert-request --cert_path`' ' to generate certs under this directory first.') - if fqdn is None: - fqdn = getfqdn_env() - common_name = f'{fqdn}'.lower() - plan.get_server(root_certificate=f'{CERT_DIR}/cert_chain.crt', - private_key=f'{CERT_DIR}/server/agg_{common_name}.key', - certificate=f'{CERT_DIR}/server/agg_{common_name}.crt').serve() + + common_name = plan.config['network']['settings']['agg_addr'].lower() + plan.get_server(root_certificate=f'{cert_dir_path}/cert_chain.crt', + private_key=f'{cert_dir_path}/server/agg_{common_name}.key', + certificate=f'{cert_dir_path}/server/agg_{common_name}.crt').serve() else: plan.get_server().serve() @@ -111,17 +106,20 @@ def generate_cert_request(fqdn, cert_path=None): server_private_key, server_csr = generate_csr(common_name, server=True) if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH/ 'cert' # NOQA - (CERT_DIR / 'server').mkdir(parents=True, exist_ok=True) + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + else: + cert_dir_path = CERT_DIR + + (cert_dir_path / 'server').mkdir(parents=True, exist_ok=True) echo(' Writing AGGREGATOR certificate key pair to: ' + style( - f'{CERT_DIR}/server', fg='green')) + f'{cert_dir_path}/server', fg='green')) # Write aggregator csr and key to disk - write_crt(server_csr, CERT_DIR / 'server' / f'{file_name}.csr') - write_key(server_private_key, CERT_DIR / 'server' / f'{file_name}.key') + write_crt(server_csr, cert_dir_path / 'server' / f'{file_name}.csr') + write_key(server_private_key, cert_dir_path / 'server' / f'{file_name}.key') # TODO: function not used @@ -171,11 +169,13 @@ def certify(fqdn, silent, cert_path=None): # Load CSR if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH/ 'cert' # NOQA + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + else: + cert_dir_path = CERT_DIR - csr_path_absolute_path = Path(CERT_DIR / f'{cert_name}.csr').absolute() + csr_path_absolute_path = Path(cert_dir_path / f'{cert_name}.csr').absolute() if not csr_path_absolute_path.exists(): echo(style('Aggregator certificate signing request not found.', fg='red') + ' Please run `fx aggregator generate-cert-request`' @@ -184,7 +184,7 @@ def certify(fqdn, silent, cert_path=None): csr, csr_hash = read_csr(csr_path_absolute_path) # Load private signing key - private_sign_key_absolute_path = Path(CERT_DIR / signing_key_path).absolute() + private_sign_key_absolute_path = Path(cert_dir_path / signing_key_path).absolute() if not private_sign_key_absolute_path.exists(): echo(style('Signing key not found.', fg='red') + ' Please run `fx workspace certify`' @@ -193,7 +193,7 @@ def certify(fqdn, silent, cert_path=None): signing_key = read_key(private_sign_key_absolute_path) # Load signing cert - signing_crt_absolute_path = Path(CERT_DIR / signing_crt_path).absolute() + signing_crt_absolute_path = Path(cert_dir_path / signing_crt_path).absolute() if not signing_crt_absolute_path.exists(): echo(style('Signing certificate not found.', fg='red') + ' Please run `fx workspace certify`' @@ -206,7 +206,7 @@ def certify(fqdn, silent, cert_path=None): + ' = ' + style(f'{csr_hash}', fg='red')) - crt_path_absolute_path = Path(CERT_DIR / f'{cert_name}.crt').absolute() + crt_path_absolute_path = Path(cert_dir_path / f'{cert_name}.crt').absolute() if silent: diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 1d2b42bd51..92ee1c2392 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -61,18 +61,18 @@ def start_(plan, collaborator_name, data_config, secure, cert_path): logger.info('🧿 Starting a Collaborator Service.') if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH / 'cert' - if not Path(CERT_DIR).exists(): + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + if not Path(cert_dir_path).exists(): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx collaborator generate-cert-request --cert_path`' ' to generate certs under this directory first.') common_name = f'{collaborator_name}'.lower() plan.get_collaborator(collaborator_name, - root_certificate=f'{CERT_DIR}/cert_chain.crt', - private_key=f'{CERT_DIR}/client/col_{common_name}.key', - certificate=f'{CERT_DIR}/client/col_{common_name}.crt').run() + root_certificate=f'{cert_dir_path}/cert_chain.crt', + private_key=f'{cert_dir_path}/client/col_{common_name}.key', + certificate=f'{cert_dir_path}/client/col_{common_name}.crt').run() else: plan.get_collaborator(collaborator_name).run() @@ -167,18 +167,20 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, ce client_private_key, client_csr = generate_csr(common_name, server=False) if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH/ 'cert' # NOQA + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + else: + cert_dir_path = CERT_DIR - (CERT_DIR / 'client').mkdir(parents=True, exist_ok=True) + (cert_dir_path / 'client').mkdir(parents=True, exist_ok=True) echo(' Moving COLLABORATOR certificate to: ' + style( - f'{CERT_DIR}', fg='green')) + f'{cert_dir_path}', fg='green')) # Write collaborator csr and key to disk - write_crt(client_csr, CERT_DIR / 'client' / f'{file_name}.csr') - write_key(client_private_key, CERT_DIR / 'client' / f'{file_name}.key') + write_crt(client_csr, cert_dir_path / 'client' / f'{file_name}.csr') + write_key(client_private_key, cert_dir_path / 'client' / f'{file_name}.key') if not skip_package: from shutil import copytree @@ -199,7 +201,7 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, ce ignore = ignore_patterns('__pycache__', '*.key', '*.srl', '*.pem') # Copy the current directory into the temporary directory - copytree(f'{CERT_DIR}/client', tmp_dir, ignore=ignore) + copytree(f'{cert_dir_path}/client', tmp_dir, ignore=ignore) for f in glob(f'{tmp_dir}/*'): if common_name not in basename(f): @@ -312,15 +314,17 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat common_name = f'{collaborator_name}'.lower() if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH/ 'cert' # NOQA + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + else: + cert_dir_path = CERT_DIR if not import_: if request_pkg: - Path(f'{CERT_DIR}/client').mkdir(parents=True, exist_ok=True) - unpack_archive(request_pkg, extract_dir=f'{CERT_DIR}/client') - csr = glob(f'{CERT_DIR}/client/*.csr')[0] + Path(f'{cert_dir_path}/client').mkdir(parents=True, exist_ok=True) + unpack_archive(request_pkg, extract_dir=f'{cert_dir_path}/client') + csr = glob(f'{cert_dir_path}/client/*.csr')[0] else: if collaborator_name is None: echo('collaborator_name can only be omitted if signing\n' @@ -329,8 +333,8 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat 'Example: fx collaborator certify --request-pkg ' 'col_one_to_agg_cert_request.zip') return - csr = glob(f'{CERT_DIR}/client/col_{common_name}.csr')[0] - copy(csr, CERT_DIR) + csr = glob(f'{cert_dir_path}/client/col_{common_name}.csr')[0] + copy(csr, cert_dir_path) cert_name = splitext(csr)[0] file_name = basename(cert_name) signing_key_path = 'ca/signing-ca/private/signing-ca.key' @@ -345,20 +349,20 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat csr, csr_hash = read_csr(f'{cert_name}.csr') # Load private signing key - if not Path(CERT_DIR / signing_key_path).exists(): + if not Path(cert_dir_path / signing_key_path).exists(): echo(style('Signing key not found.', fg='red') + ' Please run `fx workspace certify`' ' to initialize the local certificate authority.') - signing_key = read_key(CERT_DIR / signing_key_path) + signing_key = read_key(cert_dir_path / signing_key_path) # Load signing cert - if not Path(CERT_DIR / signing_crt_path).exists(): + if not Path(cert_dir_path / signing_crt_path).exists(): echo(style('Signing certificate not found.', fg='red') + ' Please run `fx workspace certify`' ' to initialize the local certificate authority.') - signing_crt = read_crt(CERT_DIR / signing_crt_path) + signing_crt = read_crt(cert_dir_path / signing_crt_path) echo('The CSR Hash for file ' + style(f'{file_name}.csr', fg='green') @@ -370,7 +374,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat echo(' Signing COLLABORATOR certificate') signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) write_crt(signed_col_cert, f'{cert_name}.crt') - register_collaborator(CERT_DIR / 'client' / f'{file_name}.crt') + register_collaborator(cert_dir_path / 'client' / f'{file_name}.crt') else: @@ -379,7 +383,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat echo(' Signing COLLABORATOR certificate') signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) write_crt(signed_col_cert, f'{cert_name}.crt') - register_collaborator(CERT_DIR / 'client' / f'{file_name}.crt') + register_collaborator(cert_dir_path / 'client' / f'{file_name}.crt') else: echo(style('Not signing certificate.', fg='red') @@ -403,18 +407,18 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat Path(f'{tmp_dir}/client').mkdir(parents=True, exist_ok=True) # Copy the signed cert to the temporary directory - copy(f'{CERT_DIR}/client/{file_name}.crt', f'{tmp_dir}/client/') + copy(f'{cert_dir_path}/client/{file_name}.crt', f'{tmp_dir}/client/') # Copy the CA certificate chain to the temporary directory - copy(f'{CERT_DIR}/cert_chain.crt', tmp_dir) + copy(f'{cert_dir_path}/cert_chain.crt', tmp_dir) # Create Zip archive of directory make_archive(archive_name, archive_type, tmp_dir) else: # Copy the signed certificate and cert chain into PKI_DIR - previous_crts = glob(f'{CERT_DIR}/client/*.crt') - unpack_archive(import_, extract_dir=CERT_DIR) - updated_crts = glob(f'{CERT_DIR}/client/*.crt') + previous_crts = glob(f'{cert_dir_path}/client/*.crt') + unpack_archive(import_, extract_dir=cert_dir_path) + updated_crts = glob(f'{cert_dir_path}/client/*.crt') cert_difference = list(set(updated_crts) - set(previous_crts)) if len(cert_difference) != 0: crt = basename(cert_difference[0]) diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index 81e95b1da6..b728b57eb5 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -235,24 +235,26 @@ def certify(cert_path=None): echo('1.1 Create Directories') if cert_path: - CERT_PATH = Path(cert_path).absolute() - (CERT_PATH / 'cert').mkdir(parents=True, exist_ok=True) - CERT_DIR = CERT_PATH/ 'cert' # NOQA + cert_path = Path(cert_path).absolute() + (cert_path / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_path / 'cert' + else: + cert_dir_path = CERT_DIR - (CERT_DIR / 'ca/root-ca/private').mkdir( + (cert_dir_path / 'ca/root-ca/private').mkdir( parents=True, exist_ok=True, mode=0o700) - (CERT_DIR / 'ca/root-ca/db').mkdir(parents=True, exist_ok=True) + (cert_dir_path / 'ca/root-ca/db').mkdir(parents=True, exist_ok=True) echo('1.2 Create Database') - with open(CERT_DIR / 'ca/root-ca/db/root-ca.db', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/root-ca/db/root-ca.db', 'w', encoding='utf-8') as f: pass # write empty file - with open(CERT_DIR / 'ca/root-ca/db/root-ca.db.attr', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/root-ca/db/root-ca.db.attr', 'w', encoding='utf-8') as f: pass # write empty file - with open(CERT_DIR / 'ca/root-ca/db/root-ca.crt.srl', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/root-ca/db/root-ca.crt.srl', 'w', encoding='utf-8') as f: f.write('01') # write file with '01' - with open(CERT_DIR / 'ca/root-ca/db/root-ca.crl.srl', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/root-ca/db/root-ca.crl.srl', 'w', encoding='utf-8') as f: f.write('01') # write file with '01' echo('1.3 Create CA Request and Certificate') @@ -263,12 +265,12 @@ def certify(cert_path=None): root_private_key, root_cert = generate_root_cert() # Write root CA certificate to disk - with open(CERT_DIR / root_crt_path, 'wb') as f: + with open(cert_dir_path / root_crt_path, 'wb') as f: f.write(root_cert.public_bytes( encoding=serialization.Encoding.PEM, )) - with open(CERT_DIR / root_key_path, 'wb') as f: + with open(cert_dir_path / root_key_path, 'wb') as f: f.write(root_private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, @@ -278,20 +280,20 @@ def certify(cert_path=None): echo('2. Create Signing Certificate') echo('2.1 Create Directories') - (CERT_DIR / 'ca/signing-ca/private').mkdir( + (cert_dir_path / 'ca/signing-ca/private').mkdir( parents=True, exist_ok=True, mode=0o700) - (CERT_DIR / 'ca/signing-ca/db').mkdir(parents=True, exist_ok=True) + (cert_dir_path / 'ca/signing-ca/db').mkdir(parents=True, exist_ok=True) echo('2.2 Create Database') - with open(CERT_DIR / 'ca/signing-ca/db/signing-ca.db', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/signing-ca/db/signing-ca.db', 'w', encoding='utf-8') as f: pass # write empty file - with open(CERT_DIR / 'ca/signing-ca/db/signing-ca.db.attr', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/signing-ca/db/signing-ca.db.attr', 'w', encoding='utf-8') as f: pass # write empty file - with open(CERT_DIR / 'ca/signing-ca/db/signing-ca.crt.srl', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/signing-ca/db/signing-ca.crt.srl', 'w', encoding='utf-8') as f: f.write('01') # write file with '01' - with open(CERT_DIR / 'ca/signing-ca/db/signing-ca.crl.srl', 'w', encoding='utf-8') as f: + with open(cert_dir_path / 'ca/signing-ca/db/signing-ca.crl.srl', 'w', encoding='utf-8') as f: f.write('01') # write file with '01' echo('2.3 Create Signing Certificate CSR') @@ -303,12 +305,12 @@ def certify(cert_path=None): signing_private_key, signing_csr = generate_signing_csr() # Write Signing CA CSR to disk - with open(CERT_DIR / signing_csr_path, 'wb') as f: + with open(cert_dir_path / signing_csr_path, 'wb') as f: f.write(signing_csr.public_bytes( encoding=serialization.Encoding.PEM, )) - with open(CERT_DIR / signing_key_path, 'wb') as f: + with open(cert_dir_path / signing_key_path, 'wb') as f: f.write(signing_private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, @@ -319,7 +321,7 @@ def certify(cert_path=None): signing_cert = sign_certificate(signing_csr, root_private_key, root_cert.subject, ca=True) - with open(CERT_DIR / signing_crt_path, 'wb') as f: + with open(cert_dir_path / signing_crt_path, 'wb') as f: f.write(signing_cert.public_bytes( encoding=serialization.Encoding.PEM, )) @@ -327,10 +329,10 @@ def certify(cert_path=None): echo('3 Create Certificate Chain') # create certificate chain file by combining root-ca and signing-ca - with open(CERT_DIR / 'cert_chain.crt', 'w', encoding='utf-8') as d: - with open(CERT_DIR / 'ca/root-ca.crt', encoding='utf-8') as s: + with open(cert_dir_path / 'cert_chain.crt', 'w', encoding='utf-8') as d: + with open(cert_dir_path / 'ca/root-ca.crt', encoding='utf-8') as s: d.write(s.read()) - with open(CERT_DIR / 'ca/signing-ca.crt') as s: + with open(cert_dir_path / 'ca/signing-ca.crt') as s: d.write(s.read()) echo('\nDone.') diff --git a/tests/github/test_pki_cert_location.sh b/tests/github/test_pki_cert_location.sh deleted file mode 100755 index 1161834e9d..0000000000 --- a/tests/github/test_pki_cert_location.sh +++ /dev/null @@ -1,146 +0,0 @@ -set -e -# Test the pipeline - -TEMPLATE=${1:-'keras_cnn_mnist'} # ['torch_cnn_mnist', 'keras_cnn_mnist'] -FED_WORKSPACE=${2:-'fed_work12345alpha81671'} # This can be whatever unique directory name you want -COL1=${3:-'one123dragons'} # This can be any unique label (lowercase) -COL2=${4:-'beta34unicorns'} # This can be any unique label (lowercase) - -FQDN=${5:-$(hostname --all-fqdns | awk '{print $1}')} - -CERT_PATH_AG=${6:-"${HOME}/.openfl/aggregator"} -CERT_PATH_COL1=${7:-"${HOME}/.openfl/one123dragons"} -CERT_PATH_COL2=${8:-"${HOME}/.openfl/beta34unicorns"} - -COL1_DATA_PATH=1 -COL2_DATA_PATH=2 - -help() { - echo "Usage: test_hello_federation.sh TEMPLATE FED_WORKSPACE COL1 COL2 [OPTIONS]" - echo - echo "Options:" - echo "--rounds-to-train rounds to train" - echo "--col1-data-path data path for collaborator 1" - echo "--col2-data-path data path for collaborator 2" - echo "--save-model path to save model in native format" - echo "-h, --help display this help and exit" -} - -# Getting additional options -ADD_OPTS=$(getopt -o "h" -l "rounds-to-train:,col1-data-path:, -col2-data-path:,save-model:,help" -n test_hello_federation.sh -- "$@") -eval set -- "$ADD_OPTS" -while (($#)); do - case "${1:-}" in - (--rounds-to-train) ROUNDS_TO_TRAIN="$2" ; shift 2 ;; - (--col1-data-path) COL1_DATA_PATH="$2" ; shift 2 ;; - (--col2-data-path) COL2_DATA_PATH="$2" ; shift 2 ;; - (--save-model) SAVE_MODEL="$2" ; shift 2 ;; - (-h|--help) help ; exit 0 ;; - - (--) shift ; break ;; - (*) echo "Invalid option: ${1:-}"; exit 1 ;; - esac -done - - - -create_collaborator() { - - FED_WORKSPACE=$1 - FED_DIRECTORY=$2 - COL=$3 - COL_DIRECTORY=$4 - DATA_PATH=$5 - CERT_PATH_COL=$6 - CERT_PATH=$7 - - ARCHIVE_NAME="${FED_WORKSPACE}.zip" - - # Copy workspace to collaborator directories (these can be on different machines) - rm -rf ${COL_DIRECTORY} # Remove any existing directory - mkdir -p ${COL_DIRECTORY} # Create a new directory for the collaborator - cd ${COL_DIRECTORY} - fx workspace import --archive ${FED_DIRECTORY}/${ARCHIVE_NAME} # Import the workspace to this collaborator - - # Create collaborator certificate request - cd ${COL_DIRECTORY}/${FED_WORKSPACE} - fx collaborator generate-cert-request -d ${DATA_PATH} -n ${COL} -c ${CERT_PATH_COL} --silent # Remove '--silent' if you run this manually - - # Sign collaborator certificate - cd ${FED_DIRECTORY} # Move back to the Aggregator - fx collaborator certify --request-pkg ${COL_DIRECTORY}/${FED_WORKSPACE}/col_${COL}_to_agg_cert_request.zip -c ${CERT_PATH} --silent # Remove '--silent' if you run this manually - - #Import the signed certificate from the aggregator - cd ${COL_DIRECTORY}/${FED_WORKSPACE} - fx collaborator certify --import ${FED_DIRECTORY}/agg_to_col_${COL}_signed_cert.zip -c ${CERT_PATH_COL} - -} - -# START -# ===== -# Make sure you are in a Python virtual environment with the FL package installed. - -# Create FL workspace -rm -rf ${FED_WORKSPACE} -fx workspace create --prefix ${FED_WORKSPACE} --template ${TEMPLATE} -cd ${FED_WORKSPACE} -FED_DIRECTORY=`pwd` # Get the absolute directory path for the workspace - -# Initialize FL plan -fx plan initialize -a ${FQDN} - -# Set rounds to train if given -if [[ ! -z "$ROUNDS_TO_TRAIN" ]] -then - sed -i "/rounds_to_train/c\ rounds_to_train: $ROUNDS_TO_TRAIN" plan/plan.yaml -fi - -# Create certificate authority for workspace -fx workspace certify -c ${CERT_PATH_AG} - -# Export FL workspace -fx workspace export - -# Create aggregator certificate -fx aggregator generate-cert-request --fqdn ${FQDN} -c ${CERT_PATH_AG} - -# Sign aggregator certificate -fx aggregator certify --fqdn ${FQDN} -c ${CERT_PATH_AG} --silent # Remove '--silent' if you run this manually - -# Create collaborator #1 -COL1_DIRECTORY=${FED_DIRECTORY}/${COL1} -create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL1} ${COL1_DIRECTORY} ${COL1_DATA_PATH} ${CERT_PATH_COL1} ${CERT_PATH_AG} - -# Create collaborator #2 -COL2_DIRECTORY=${FED_DIRECTORY}/${COL2} -create_collaborator ${FED_WORKSPACE} ${FED_DIRECTORY} ${COL2} ${COL2_DIRECTORY} ${COL2_DATA_PATH} ${CERT_PATH_COL2} ${CERT_PATH_AG} - -# # Run the federation -cd ${FED_DIRECTORY} -fx aggregator start -c ${CERT_PATH_AG} --fqdn ${FQDN} & -sleep 5 -cd ${COL1_DIRECTORY}/${FED_WORKSPACE} -fx collaborator start -n ${COL1} -c ${CERT_PATH_COL1} & -cd ${COL2_DIRECTORY}/${FED_WORKSPACE} -fx collaborator start -n ${COL2} -c ${CERT_PATH_COL2} -wait - -# # Convert model to native format -if [[ ! -z "$SAVE_MODEL" ]] -then - cd ${FED_DIRECTORY} - fx model save -i "./save/${TEMPLATE}_last.pbuf" -o ${SAVE_MODEL} -fi - -# Clear cert directories - -cd ${FED_DIRECTORY} -fx aggregator uninstall-cert -c ${CERT_PATH_AG} -cd ${COL1_DIRECTORY}/${FED_WORKSPACE} -fx collaborator uninstall-cert -c ${CERT_PATH_COL1} -cd ${COL2_DIRECTORY}/${FED_WORKSPACE} -fx collaborator uninstall-cert -c ${CERT_PATH_COL2} - -rm -rf ${FED_DIRECTORY} - From 50c59c22a8e02340dda52f811fb5889b0196b7ee Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Wed, 4 Jan 2023 06:16:38 -0800 Subject: [PATCH 07/19] refining tests Signed-off-by: Mansi Sharma --- .github/workflows/pki.yml | 16 ++++ tests/github/test_pki_cert_location.py | 100 +++++++++++++++++++++++++ tests/github/utils.py | 73 ++++++++++++------ 3 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 tests/github/test_pki_cert_location.py diff --git a/.github/workflows/pki.yml b/.github/workflows/pki.yml index 2bea96efe7..224c0d40c0 100644 --- a/.github/workflows/pki.yml +++ b/.github/workflows/pki.yml @@ -47,4 +47,20 @@ jobs: - name: Test PKI run: | python tests/github/pki_wrong_cn.py + test_pki_cert_location: + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v3 + with: + python-version: "3.8" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + - name: Test PKI + run: | + python tests/github/test_pki_cert_location.py diff --git a/tests/github/test_pki_cert_location.py b/tests/github/test_pki_cert_location.py new file mode 100644 index 0000000000..cfd7b72d3e --- /dev/null +++ b/tests/github/test_pki_cert_location.py @@ -0,0 +1,100 @@ +# Copyright (C) 2020-2021 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +import time +import socket +import argparse +from pathlib import Path +from subprocess import check_call +from concurrent.futures import ProcessPoolExecutor + +from openfl.utilities.utils import rmtree +from tests.github.utils import create_collaborator, create_certified_workspace, certify_aggregator + + +if __name__ == '__main__': + # Test the pipeline + parser = argparse.ArgumentParser() + workspace_choice = [] + with os.scandir('openfl-workspace') as iterator: + for entry in iterator: + if entry.name not in ['__init__.py', 'workspace', 'default']: + workspace_choice.append(entry.name) + parser.add_argument('--template', default='keras_cnn_mnist', choices=workspace_choice) + parser.add_argument('--fed_workspace', default='fed_work12345alpha81671') + parser.add_argument('--col1', default='one123dragons') + parser.add_argument('--col2', default='beta34unicorns') + parser.add_argument('--rounds-to-train') + parser.add_argument('--col1-data-path', default='1') + parser.add_argument('--col2-data-path', default='2') + parser.add_argument('--agg-cert-path', default=Path.cwd()) + parser.add_argument('--col1-cert-path', default=Path.cwd()) + parser.add_argument('--col2-cert-path', default=Path.cwd()) + parser.add_argument('--save-model') + + origin_dir = Path.cwd().resolve() + args = parser.parse_args() + fed_workspace = args.fed_workspace + archive_name = f'{fed_workspace}.zip' + fqdn = socket.getfqdn() + template = args.template + rounds_to_train = args.rounds_to_train + col1, col2 = args.col1, args.col2 + col1_data_path, col2_data_path = args.col1_data_path, args.col2_data_path + agg_cert_path = Path(args.agg_cert_path).resolve() + col1_cert_path = Path(args.col1_cert_path).resolve() + col2_cert_path = Path(args.col2_cert_path).resolve() + save_model = args.save_model + + # START + # ===== + # Make sure you are in a Python virtual environment with the FL package installed. + create_certified_workspace(fed_workspace, template, fqdn, rounds_to_train, agg_cert_path) + certify_aggregator(fqdn, agg_cert_path) + + workspace_root = Path().resolve() # Get the absolute directory path for the workspace + + # Create collaborator #1 + create_collaborator(col1, workspace_root, col1_data_path, archive_name, + fed_workspace, col1_cert_path, agg_cert_path) + + # Create collaborator #2 + create_collaborator(col2, workspace_root, col2_data_path, archive_name, + fed_workspace, col2_cert_path, agg_cert_path) + + # Run the federation + with ProcessPoolExecutor(max_workers=3) as executor: + executor.submit( + check_call, ['fx', 'aggregator', 'start', '-c', agg_cert_path], + cwd=workspace_root) + time.sleep(5) + + dir1 = workspace_root / col1 / fed_workspace + executor.submit( + check_call, ['fx', 'collaborator', 'start', '-n', col1, '-c', col1_cert_path], + cwd=dir1) + + dir2 = workspace_root / col2 / fed_workspace + executor.submit( + check_call, ['fx', 'collaborator', 'start', '-n', col2, '-c', col2_cert_path], + cwd=dir2) + + # Convert model to native format + if save_model: + check_call( + ['fx', 'model', 'save', '-i', f'./save/{template}_last.pbuf', '-o', save_model], + cwd=workspace_root) + + # Clear cert paths + check_call( + ['fx', 'aggregator', 'uninstall-cert', '-c', agg_cert_path], + cwd=workspace_root) + check_call( + ['fx', 'collaborator', 'uninstall-cert', '-c', col1_cert_path], + cwd=workspace_root / col1 / fed_workspace) + check_call( + ['fx', 'collaborator', 'uninstall-cert', '-c', col2_cert_path], + cwd=workspace_root / col2 / fed_workspace) + os.chdir(origin_dir) + rmtree(workspace_root) diff --git a/tests/github/utils.py b/tests/github/utils.py index 96f283accd..f6a5a8be0e 100644 --- a/tests/github/utils.py +++ b/tests/github/utils.py @@ -8,7 +8,8 @@ import tarfile -def create_collaborator(col, workspace_root, data_path, archive_name, fed_workspace): +def create_collaborator(col, workspace_root, data_path, archive_name, fed_workspace, + cert_path=None, ca_cert_path=None): # Copy workspace to collaborator directories (these can be on different machines) col_path = workspace_root / col shutil.rmtree(col_path, ignore_errors=True) # Remove any existing directory @@ -22,27 +23,47 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_worksp # Create collaborator certificate request # Remove '--silent' if you run this manually - check_call( - ['fx', 'collaborator', 'generate-cert-request', '-d', data_path, '-n', col, '--silent'], - cwd=col_path / fed_workspace - ) + if cert_path: + check_call( + ['fx', 'collaborator', 'generate-cert-request', '-d', data_path, + '-n', col, '-c', cert_path, '--silent'], + cwd=col_path / fed_workspace + ) + else: + check_call( + ['fx', 'collaborator', 'generate-cert-request', '-d', data_path, + '-n', col, '--silent'], + cwd=col_path / fed_workspace + ) # Sign collaborator certificate # Remove '--silent' if you run this manually request_pkg = col_path / fed_workspace / f'col_{col}_to_agg_cert_request.zip' - check_call( - ['fx', 'collaborator', 'certify', '--request-pkg', str(request_pkg), '--silent'], - cwd=workspace_root) + if ca_cert_path: + check_call( + ['fx', 'collaborator', 'certify', '--request-pkg', str(request_pkg), + '-c', ca_cert_path, '--silent'], + cwd=workspace_root) + else: + check_call( + ['fx', 'collaborator', 'certify', '--request-pkg', str(request_pkg), '--silent'], + cwd=workspace_root) # Import the signed certificate from the aggregator import_path = workspace_root / f'agg_to_col_{col}_signed_cert.zip' - check_call( - ['fx', 'collaborator', 'certify', '--import', import_path], - cwd=col_path / fed_workspace - ) - - -def create_certified_workspace(path, template, fqdn, rounds_to_train): + if cert_path: + check_call( + ['fx', 'collaborator', 'certify', '--import', import_path, '-c', cert_path], + cwd=col_path / fed_workspace + ) + else: + check_call( + ['fx', 'collaborator', 'certify', '--import', import_path], + cwd=col_path / fed_workspace + ) + + +def create_certified_workspace(path, template, fqdn, rounds_to_train, cert_path=None): shutil.rmtree(path, ignore_errors=True) check_call(['fx', 'workspace', 'create', '--prefix', path, '--template', template]) os.chdir(path) @@ -62,18 +83,26 @@ def create_certified_workspace(path, template, fqdn, rounds_to_train): except (ValueError, TypeError): pass # Create certificate authority for workspace - check_call(['fx', 'workspace', 'certify']) + if cert_path: + check_call(['fx', 'workspace', 'certify', '-c', cert_path]) + else: + check_call(['fx', 'workspace', 'certify']) # Export FL workspace check_call(['fx', 'workspace', 'export']) -def certify_aggregator(fqdn): - # Create aggregator certificate - check_call(['fx', 'aggregator', 'generate-cert-request', '--fqdn', fqdn]) - - # Sign aggregator certificate - check_call(['fx', 'aggregator', 'certify', '--fqdn', fqdn, '--silent']) +def certify_aggregator(fqdn, cert_path=None): + if cert_path: + # Create aggregator certificate + check_call(['fx', 'aggregator', 'generate-cert-request', '--fqdn', fqdn, '-c', cert_path]) + # Sign aggregator certificate + check_call(['fx', 'aggregator', 'certify', '--fqdn', fqdn, '-c', cert_path, '--silent']) + else: + # Create aggregator certificate + check_call(['fx', 'aggregator', 'generate-cert-request', '--fqdn', fqdn]) + # Sign aggregator certificate + check_call(['fx', 'aggregator', 'certify', '--fqdn', fqdn, '--silent']) def create_signed_cert_for_collaborator(col, data_path): From f1e1450c108b5b35809e3d330fb8fdaf9823e076 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 13:49:31 -0800 Subject: [PATCH 08/19] parameterize cert and key locations Signed-off-by: Mansi Sharma --- .github/workflows/pki-certs-location.yml | 34 +++ .github/workflows/pki.yml | 16 - openfl/interface/aggregator.py | 219 +++++++++----- openfl/interface/collaborator.py | 367 +++++++++++++++-------- openfl/interface/workspace.py | 97 ++++-- tests/github/test_pki_cert_location.py | 32 +- tests/github/utils.py | 33 +- 7 files changed, 535 insertions(+), 263 deletions(-) create mode 100644 .github/workflows/pki-certs-location.yml diff --git a/.github/workflows/pki-certs-location.yml b/.github/workflows/pki-certs-location.yml new file mode 100644 index 0000000000..d24be212a2 --- /dev/null +++ b/.github/workflows/pki-certs-location.yml @@ -0,0 +1,34 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Test PKI certs location + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: ['ubuntu-latest', 'windows-latest'] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.8 + uses: actions/setup-python@v3 + with: + python-version: "3.8" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install . + - name: Test PKI certs location + run: | + python -m tests.github.test_pki_cert_location --agg-cert-path ~/.openfl/agg --agg-key-path ~/.openfl/agg --col1-cert-path ~/.openfl/col1/ --col1-key-path ~/.openfl/col1 --col2-cert-path ~/.openfl/col2 --col2-key-path ~/.openfl/col2/ diff --git a/.github/workflows/pki.yml b/.github/workflows/pki.yml index 224c0d40c0..2bea96efe7 100644 --- a/.github/workflows/pki.yml +++ b/.github/workflows/pki.yml @@ -47,20 +47,4 @@ jobs: - name: Test PKI run: | python tests/github/pki_wrong_cn.py - test_pki_cert_location: - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.8 - uses: actions/setup-python@v3 - with: - python-version: "3.8" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install . - - name: Test PKI - run: | - python tests/github/test_pki_cert_location.py diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 618adae255..7358e5efbd 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -37,8 +37,10 @@ def aggregator(context): @option('-s', '--secure', required=False, help='Enable Intel SGX Enclave', is_flag=True, default=False) @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) -def start_(plan, authorized_cols, secure, cert_path): + help='The path where aggregator certificate resides', required=False) +@option('-k', '--key_path', + help='The path where aggregator key resides', required=False) +def start_(plan, authorized_cols, secure, cert_path, key_path): """Start the aggregator service.""" from pathlib import Path @@ -56,19 +58,18 @@ def start_(plan, authorized_cols, secure, cert_path): logger.info('🧿 Starting the Aggregator Service.') - if cert_path: + if cert_path and key_path: cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' - if not Path(cert_dir_path).exists(): + key_path = Path(key_path).absolute() + if not Path(cert_path).exists() or not Path(key_path).exists(): echo(style('Certificate Path not found.', fg='red') + ' Please run `fx aggregator generate-cert-request --cert_path`' ' to generate certs under this directory first.') common_name = plan.config['network']['settings']['agg_addr'].lower() - plan.get_server(root_certificate=f'{cert_dir_path}/cert_chain.crt', - private_key=f'{cert_dir_path}/server/agg_{common_name}.key', - certificate=f'{cert_dir_path}/server/agg_{common_name}.crt').serve() + plan.get_server(root_certificate=f'{cert_path}/cert_chain.crt', + private_key=f'{cert_path}/agg_{common_name}.key', + certificate=f'{key_path}/agg_{common_name}.crt').serve() else: plan.get_server().serve() @@ -79,12 +80,14 @@ def start_(plan, authorized_cols, secure, cert_path): f' aggregator node [{getfqdn_env()}]', default=getfqdn_env()) @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) -def _generate_cert_request(fqdn, cert_path): - generate_cert_request(fqdn, cert_path) + help='The path where aggregator certificate will reside', required=False) +@option('-k', '--key_path', + help='The path where aggregator key will reside', required=False) +def _generate_cert_request(fqdn, cert_path, key_path): + generate_cert_request(fqdn, cert_path, key_path) -def generate_cert_request(fqdn, cert_path=None): +def generate_cert_request(fqdn, cert_path=None, key_path=None): """Create aggregator certificate key pair.""" from pathlib import Path from openfl.cryptography.participant import generate_csr @@ -105,21 +108,29 @@ def generate_cert_request(fqdn, cert_path=None): server_private_key, server_csr = generate_csr(common_name, server=True) - if cert_path: + if cert_path and key_path: cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' - else: - cert_dir_path = CERT_DIR + key_path = Path(key_path).absolute() + + echo(' Writing AGGREGATOR certificate to: ' + style( + f'{cert_path}', fg='green')) + echo(' Writing AGGREGATOR key to: ' + style( + f'{key_path}', fg='green')) - (cert_dir_path / 'server').mkdir(parents=True, exist_ok=True) + # Write aggregator csr and key to disk + write_crt(server_csr, cert_path / f'{file_name}.csr') + write_key(server_private_key, key_path / f'{file_name}.key') + else: + if cert_path and not key_path or not cert_path and key_path: + echo(f'Both cert_path and key_path should be provided. Using default {CERT_DIR}.') + (CERT_DIR / 'server').mkdir(parents=True, exist_ok=True) - echo(' Writing AGGREGATOR certificate key pair to: ' + style( - f'{cert_dir_path}/server', fg='green')) + echo(' Writing AGGREGATOR certificate key pair to: ' + style( + f'{CERT_DIR}/server', fg='green')) - # Write aggregator csr and key to disk - write_crt(server_csr, cert_dir_path / 'server' / f'{file_name}.csr') - write_key(server_private_key, cert_dir_path / 'server' / f'{file_name}.key') + # Write aggregator csr and key to disk + write_crt(server_csr, CERT_DIR / 'server' / f'{file_name}.csr') + write_key(server_private_key, CERT_DIR / 'server' / f'{file_name}.key') # TODO: function not used @@ -140,12 +151,14 @@ def find_certificate_name(file_name): default=getfqdn_env()) @option('-s', '--silent', help='Do not prompt', is_flag=True) @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) -def _certify(fqdn, silent, cert_path): - certify(fqdn, silent, cert_path) + help='The path where signing CA certificate resides', required=False) +@option('-k', '--key_path', + help='The path where signing CA key resides', required=False) +def _certify(fqdn, silent, cert_path, key_path): + certify(fqdn, silent, cert_path, key_path) -def certify(fqdn, silent, cert_path=None): +def certify(fqdn, silent, cert_path=None, key_path=None): """Sign/certify the aggregator certificate key pair.""" from pathlib import Path @@ -163,82 +176,140 @@ def certify(fqdn, silent, cert_path=None): common_name = f'{fqdn}'.lower() file_name = f'agg_{common_name}' - cert_name = f'server/{file_name}' - signing_key_path = 'ca/signing-ca/private/signing-ca.key' - signing_crt_path = 'ca/signing-ca.crt' # Load CSR - if cert_path: + if cert_path and key_path: cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' - else: - cert_dir_path = CERT_DIR + key_path = Path(key_path).absolute() + + agg_cert_name = f'{file_name}' + csr_path_absolute_path = Path(cert_path / f'{agg_cert_name}.csr').absolute() + + if not csr_path_absolute_path.exists(): + echo(style('Aggregator certificate signing request not found.', fg='red') + + ' Please run `fx aggregator generate-cert-request -c -k`' + ' to generate the certificate request.') - csr_path_absolute_path = Path(cert_dir_path / f'{cert_name}.csr').absolute() - if not csr_path_absolute_path.exists(): - echo(style('Aggregator certificate signing request not found.', fg='red') - + ' Please run `fx aggregator generate-cert-request`' - ' to generate the certificate request.') + csr, csr_hash = read_csr(csr_path_absolute_path) - csr, csr_hash = read_csr(csr_path_absolute_path) + # Load private signing key + signing_key_path = 'signing-ca.key' + private_sign_key_absolute_path = Path(cert_path / signing_key_path).absolute() + if not private_sign_key_absolute_path.exists(): + echo(style('Signing key not found.', fg='red') + + ' Please run `fx workspace certify -c -k`' + ' to initialize the local certificate authority.') - # Load private signing key - private_sign_key_absolute_path = Path(cert_dir_path / signing_key_path).absolute() - if not private_sign_key_absolute_path.exists(): - echo(style('Signing key not found.', fg='red') - + ' Please run `fx workspace certify`' - ' to initialize the local certificate authority.') + signing_key = read_key(private_sign_key_absolute_path) - signing_key = read_key(private_sign_key_absolute_path) + # Load signing cert + signing_crt_path = 'signing-ca.crt' + signing_crt_absolute_path = Path(cert_path / signing_crt_path).absolute() + if not signing_crt_absolute_path.exists(): + echo(style('Signing certificate not found.', fg='red') + + ' Please run `fx workspace certify -c -k`' + ' to initialize the local certificate authority.') - # Load signing cert - signing_crt_absolute_path = Path(cert_dir_path / signing_crt_path).absolute() - if not signing_crt_absolute_path.exists(): - echo(style('Signing certificate not found.', fg='red') - + ' Please run `fx workspace certify`' - ' to initialize the local certificate authority.') + signing_crt = read_crt(signing_crt_absolute_path) - signing_crt = read_crt(signing_crt_absolute_path) + echo('The CSR Hash for file ' + + style(f'{agg_cert_name}.csr', fg='green') + + ' = ' + + style(f'{csr_hash}', fg='red')) - echo('The CSR Hash for file ' - + style(f'{cert_name}.csr', fg='green') - + ' = ' - + style(f'{csr_hash}', fg='red')) + crt_path_absolute_path = Path(cert_path / f'{agg_cert_name}.crt').absolute() - crt_path_absolute_path = Path(cert_dir_path / f'{cert_name}.crt').absolute() + if silent: + + echo(' Signing AGGREGATOR certificate') + signed_agg_cert = sign_certificate(csr, signing_key, signing_crt.subject) + write_crt(signed_agg_cert, crt_path_absolute_path) + + else: - if silent: + if confirm('Do you want to sign this certificate?'): - echo(' Signing AGGREGATOR certificate') - signed_agg_cert = sign_certificate(csr, signing_key, signing_crt.subject) - write_crt(signed_agg_cert, crt_path_absolute_path) + echo(' Signing AGGREGATOR certificate') + signed_agg_cert = sign_certificate(csr, signing_key, signing_crt.subject) + write_crt(signed_agg_cert, crt_path_absolute_path) + + else: + echo(style('Not signing certificate.', fg='red') + + ' Please check with this AGGREGATOR to get the correct' + ' certificate for this federation.') else: + agg_cert_name = f'server/{file_name}' + signing_key_path = 'ca/signing-ca/private/signing-ca.key' + signing_crt_path = 'ca/signing-ca.crt' + csr_path_absolute_path = Path(CERT_DIR / f'{agg_cert_name}.csr').absolute() + if not csr_path_absolute_path.exists(): + echo(style('Aggregator certificate signing request not found.', fg='red') + + ' Please run `fx aggregator generate-cert-request`' + ' to generate the certificate request.') + + csr, csr_hash = read_csr(csr_path_absolute_path) + + # Load private signing key + private_sign_key_absolute_path = Path(CERT_DIR / signing_key_path).absolute() + if not private_sign_key_absolute_path.exists(): + echo(style('Signing key not found.', fg='red') + + ' Please run `fx workspace certify`' + ' to initialize the local certificate authority.') + + signing_key = read_key(private_sign_key_absolute_path) + + # Load signing cert + signing_crt_absolute_path = Path(CERT_DIR / signing_crt_path).absolute() + if not signing_crt_absolute_path.exists(): + echo(style('Signing certificate not found.', fg='red') + + ' Please run `fx workspace certify`' + ' to initialize the local certificate authority.') + + signing_crt = read_crt(signing_crt_absolute_path) - if confirm('Do you want to sign this certificate?'): + echo('The CSR Hash for file ' + + style(f'{agg_cert_name}.csr', fg='green') + + ' = ' + + style(f'{csr_hash}', fg='red')) + + crt_path_absolute_path = Path(CERT_DIR / f'{agg_cert_name}.crt').absolute() + + if silent: echo(' Signing AGGREGATOR certificate') signed_agg_cert = sign_certificate(csr, signing_key, signing_crt.subject) write_crt(signed_agg_cert, crt_path_absolute_path) else: - echo(style('Not signing certificate.', fg='red') - + ' Please check with this AGGREGATOR to get the correct' - ' certificate for this federation.') + + if confirm('Do you want to sign this certificate?'): + + echo(' Signing AGGREGATOR certificate') + signed_agg_cert = sign_certificate(csr, signing_key, signing_crt.subject) + write_crt(signed_agg_cert, crt_path_absolute_path) + + else: + echo(style('Not signing certificate.', fg='red') + + ' Please check with this AGGREGATOR to get the correct' + ' certificate for this federation.') @aggregator.command(name='uninstall-cert') @option('-c', '--cert_path', help='The cert path where pki certs reside', required=True) -def _uninstall_cert(cert_path): - uninstall_cert(cert_path) +@option('-k', '--key_path', + help='The key path where key reside', required=True) +def _uninstall_cert(cert_path, key_path): + uninstall_cert(cert_path, key_path) -def uninstall_cert(cert_path=None): +def uninstall_cert(cert_path=None, key_path=None): """Uninstall certs under a given directory.""" - import shutil + from openfl.utilities.utils import rmtree from pathlib import Path cert_path = Path(cert_path).absolute() - shutil.rmtree(cert_path, ignore_errors=True) + rmtree(cert_path, ignore_errors=True) + key_path = Path(key_path).absolute() + rmtree(key_path, ignore_errors=True) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 92ee1c2392..9dc62a24f5 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -38,8 +38,10 @@ def collaborator(context): @option('-s', '--secure', required=False, help='Enable Intel SGX Enclave', is_flag=True, default=False) @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) -def start_(plan, collaborator_name, data_config, secure, cert_path): + help='The path where collaborator certificate resides', required=False) +@option('-k', '--key_path', + help='The path where collaborator key resides', required=False) +def start_(plan, collaborator_name, data_config, secure, cert_path, key_path): """Start a collaborator service.""" from pathlib import Path @@ -60,19 +62,18 @@ def start_(plan, collaborator_name, data_config, secure, cert_path): echo(f'Data = {plan.cols_data_paths}') logger.info('🧿 Starting a Collaborator Service.') - if cert_path: + if cert_path and key_path: cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' - if not Path(cert_dir_path).exists(): + key_path = Path(key_path).absolute() + if not Path(cert_path).exists() or not Path(key_path).exists(): echo(style('Certificate Path not found.', fg='red') - + ' Please run `fx collaborator generate-cert-request --cert_path`' + + ' Please run `fx collaborator generate-cert-request -c -k`' ' to generate certs under this directory first.') common_name = f'{collaborator_name}'.lower() plan.get_collaborator(collaborator_name, - root_certificate=f'{cert_dir_path}/cert_chain.crt', - private_key=f'{cert_dir_path}/client/col_{common_name}.key', - certificate=f'{cert_dir_path}/client/col_{common_name}.crt').run() + root_certificate=f'{cert_path}/cert_chain.crt', + private_key=f'{cert_path}/col_{common_name}.key', + certificate=f'{cert_path}/col_{common_name}.crt').run() else: plan.get_collaborator(collaborator_name).run() @@ -134,17 +135,20 @@ def register_data_path(collaborator_name, data_path=None, silent=False): help='Do not package the certificate signing request for export', is_flag=True) @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) + help='The path where collaborator certificate resides', required=False) +@option('-k', '--key_path', + help='The path where collaborator key resides', required=False) def generate_cert_request_(collaborator_name, - data_path, silent, skip_package, cert_path): + data_path, silent, skip_package, cert_path, key_path): """Generate certificate request for the collaborator.""" if data_path and is_directory_traversal(data_path): echo('Data path is out of the openfl workspace scope.') sys.exit(1) - generate_cert_request(collaborator_name, data_path, silent, skip_package, cert_path) + generate_cert_request(collaborator_name, data_path, silent, skip_package, cert_path, key_path) -def generate_cert_request(collaborator_name, data_path, silent, skip_package, cert_path=None): +def generate_cert_request(collaborator_name, data_path, silent, skip_package, + cert_path=None, key_path=None): """ Create collaborator certificate key pair. @@ -166,21 +170,27 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, ce client_private_key, client_csr = generate_csr(common_name, server=False) - if cert_path: + if cert_path and key_path: cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' - else: - cert_dir_path = CERT_DIR + key_path = Path(key_path).absolute() + + echo(' Moving COLLABORATOR certificate to: ' + style( + f'{cert_path}', fg='green')) + echo(' Moving COLLABORATOR key to: ' + style( + f'{key_path}', fg='green')) - (cert_dir_path / 'client').mkdir(parents=True, exist_ok=True) + # Write collaborator csr and key to disk + write_crt(client_csr, cert_path / f'{file_name}.csr') + write_key(client_private_key, key_path / f'{file_name}.key') + else: + (CERT_DIR / 'client').mkdir(parents=True, exist_ok=True) - echo(' Moving COLLABORATOR certificate to: ' + style( - f'{cert_dir_path}', fg='green')) + echo(' Moving COLLABORATOR certificate key pair to: ' + style( + f'{CERT_DIR}', fg='green')) - # Write collaborator csr and key to disk - write_crt(client_csr, cert_dir_path / 'client' / f'{file_name}.csr') - write_key(client_private_key, cert_dir_path / 'client' / f'{file_name}.key') + # Write collaborator csr and key to disk + write_crt(client_csr, CERT_DIR / 'client' / f'{file_name}.csr') + write_key(client_private_key, CERT_DIR / 'client' / f'{file_name}.key') if not skip_package: from shutil import copytree @@ -201,7 +211,11 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, ce ignore = ignore_patterns('__pycache__', '*.key', '*.srl', '*.pem') # Copy the current directory into the temporary directory - copytree(f'{cert_dir_path}/client', tmp_dir, ignore=ignore) + if cert_path: + cert_path = Path(cert_path).absolute() + copytree(cert_path, tmp_dir, ignore=ignore) + else: + copytree(f'{CERT_DIR}/client', tmp_dir, ignore=ignore) for f in glob(f'{tmp_dir}/*'): if common_name not in basename(f): @@ -285,13 +299,16 @@ def register_collaborator(file_name): help='Import the archive containing the collaborator\'s' ' certificate (signed by the CA)') @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) -def certify_(collaborator_name, silent, request_pkg, import_, cert_path): + help='The path where signing CA certificate resides', required=False) +@option('-k', '--key_path', + help='The path where signing CA key resides', required=False) +def certify_(collaborator_name, silent, request_pkg, import_, cert_path, key_path): """Certify the collaborator.""" - certify(collaborator_name, silent, request_pkg, import_, cert_path) + certify(collaborator_name, silent, request_pkg, import_, cert_path, key_path) -def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_path=None): +def certify(collaborator_name, silent, request_pkg=None, import_=False, + cert_path=None, key_path=None): """Sign/certify collaborator certificate key pair.""" from click import confirm from pathlib import Path @@ -313,131 +330,241 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, cert_pat common_name = f'{collaborator_name}'.lower() - if cert_path: + if cert_path and key_path: cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' - else: - cert_dir_path = CERT_DIR + key_path = Path(key_path).absolute() - if not import_: - if request_pkg: - Path(f'{cert_dir_path}/client').mkdir(parents=True, exist_ok=True) - unpack_archive(request_pkg, extract_dir=f'{cert_dir_path}/client') - csr = glob(f'{cert_dir_path}/client/*.csr')[0] - else: - if collaborator_name is None: - echo('collaborator_name can only be omitted if signing\n' - 'a zipped request package.\n' - '\n' - 'Example: fx collaborator certify --request-pkg ' - 'col_one_to_agg_cert_request.zip') - return - csr = glob(f'{cert_dir_path}/client/col_{common_name}.csr')[0] - copy(csr, cert_dir_path) - cert_name = splitext(csr)[0] - file_name = basename(cert_name) - signing_key_path = 'ca/signing-ca/private/signing-ca.key' - signing_crt_path = 'ca/signing-ca.crt' + if not import_: + if request_pkg: + unpack_archive(request_pkg, extract_dir=cert_path) + csr = glob(f'{cert_path}/*.csr')[0] + print("csr is", csr) + else: + if collaborator_name is None: + echo('collaborator_name can only be omitted if signing\n' + 'a zipped request package.\n' + '\n' + 'Example: fx collaborator certify --request-pkg ' + 'col_one_to_agg_cert_request.zip') + return + csr = glob(f'{cert_path}/col_{common_name}.csr')[0] + print("Csr is", csr) + copy(csr, cert_path) + cert_name = splitext(csr)[0] + print("cert name", cert_name) + file_name = basename(cert_name) + print("file name", file_name) + signing_key_path = 'signing-ca.key' + signing_crt_path = 'signing-ca.crt' + + # Load CSR + if not Path(f'{cert_name}.csr').exists(): + echo(style('Collaborator certificate signing request not found.', fg='red') + + ' Please run `fx collaborator generate-cert-request -c -k`' + ' to generate the certificate request.') + + csr, csr_hash = read_csr(f'{cert_name}.csr') + + # Load private signing key + if not Path(key_path / signing_key_path).exists(): + echo(style('Signing key not found.', fg='red') + + ' Please run `fx workspace certify`' + ' to initialize the local certificate authority.') + + signing_key = read_key(key_path / signing_key_path) + + # Load signing cert + if not Path(cert_path / signing_crt_path).exists(): + echo(style('Signing certificate not found.', fg='red') + + ' Please run `fx workspace certify`' + ' to initialize the local certificate authority.') + + signing_crt = read_crt(cert_path / signing_crt_path) + + echo('The CSR Hash for file ' + + style(f'{file_name}.csr', fg='green') + + ' = ' + + style(f'{csr_hash}', fg='red')) + + if silent: - # Load CSR - if not Path(f'{cert_name}.csr').exists(): - echo(style('Collaborator certificate signing request not found.', fg='red') - + ' Please run `fx collaborator generate-cert-request`' - ' to generate the certificate request.') + echo(' Signing COLLABORATOR certificate') + signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) + write_crt(signed_col_cert, f'{cert_name}.crt') + register_collaborator(cert_path / f'{file_name}.crt') - csr, csr_hash = read_csr(f'{cert_name}.csr') + else: - # Load private signing key - if not Path(cert_dir_path / signing_key_path).exists(): - echo(style('Signing key not found.', fg='red') - + ' Please run `fx workspace certify`' - ' to initialize the local certificate authority.') + if confirm('Do you want to sign this certificate?'): - signing_key = read_key(cert_dir_path / signing_key_path) + echo(' Signing COLLABORATOR certificate') + signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) + write_crt(signed_col_cert, f'{cert_name}.crt') + register_collaborator(cert_path / f'{file_name}.crt') - # Load signing cert - if not Path(cert_dir_path / signing_crt_path).exists(): - echo(style('Signing certificate not found.', fg='red') - + ' Please run `fx workspace certify`' - ' to initialize the local certificate authority.') + else: + echo(style('Not signing certificate.', fg='red') + + ' Please check with this collaborator to get the' + ' correct certificate for this federation.') + return - signing_crt = read_crt(cert_dir_path / signing_crt_path) + if len(common_name) == 0: + # If the collaborator name is provided, the collaborator and + # certificate does not need to be exported + return - echo('The CSR Hash for file ' - + style(f'{file_name}.csr', fg='green') - + ' = ' - + style(f'{csr_hash}', fg='red')) + # Remove unneeded CSR + remove(f'{cert_name}.csr') - if silent: + archive_type = 'zip' + archive_name = f'agg_to_{file_name}_signed_cert' - echo(' Signing COLLABORATOR certificate') - signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) - write_crt(signed_col_cert, f'{cert_name}.crt') - register_collaborator(cert_dir_path / 'client' / f'{file_name}.crt') + # Collaborator certificate signing request + tmp_dir = join(mkdtemp(), 'openfl', archive_name) - else: + Path(tmp_dir).mkdir(parents=True, exist_ok=True) + # Copy the signed cert to the temporary directory + copy(f'{cert_path}/{file_name}.crt', f'{tmp_dir}/') + # Copy the CA certificate chain to the temporary directory + copy(f'{cert_path}/cert_chain.crt', tmp_dir) - if confirm('Do you want to sign this certificate?'): + # Create Zip archive of directory + make_archive(archive_name, archive_type, tmp_dir) + + else: + # Copy the signed certificate and cert chain into PKI_DIR + previous_crts = glob(f'{cert_path}/*.crt') + unpack_archive(import_, extract_dir=cert_path) + updated_crts = glob(f'{cert_path}/*.crt') + cert_difference = list(set(updated_crts) - set(previous_crts)) + if len(cert_difference) != 0: + crt = basename(cert_difference[0]) + echo(f'Certificate {crt} installed to PKI directory') + else: + echo('Certificate updated in the PKI directory') + else: + if not import_: + if request_pkg: + Path(f'{CERT_DIR}/client').mkdir(parents=True, exist_ok=True) + unpack_archive(request_pkg, extract_dir=f'{CERT_DIR}/client') + csr = glob(f'{CERT_DIR}/client/*.csr')[0] + else: + if collaborator_name is None: + echo('collaborator_name can only be omitted if signing\n' + 'a zipped request package.\n' + '\n' + 'Example: fx collaborator certify --request-pkg ' + 'col_one_to_agg_cert_request.zip') + return + csr = glob(f'{CERT_DIR}/client/col_{common_name}.csr')[0] + copy(csr, CERT_DIR) + cert_name = splitext(csr)[0] + file_name = basename(cert_name) + signing_key_path = 'ca/signing-ca/private/signing-ca.key' + signing_crt_path = 'ca/signing-ca.crt' + + # Load CSR + if not Path(f'{cert_name}.csr').exists(): + echo(style('Collaborator certificate signing request not found.', fg='red') + + ' Please run `fx collaborator generate-cert-request`' + ' to generate the certificate request.') + + csr, csr_hash = read_csr(f'{cert_name}.csr') + + # Load private signing key + if not Path(CERT_DIR / signing_key_path).exists(): + echo(style('Signing key not found.', fg='red') + + ' Please run `fx workspace certify`' + ' to initialize the local certificate authority.') + + signing_key = read_key(CERT_DIR / signing_key_path) + + # Load signing cert + if not Path(CERT_DIR / signing_crt_path).exists(): + echo(style('Signing certificate not found.', fg='red') + + ' Please run `fx workspace certify`' + ' to initialize the local certificate authority.') + + signing_crt = read_crt(CERT_DIR / signing_crt_path) + + echo('The CSR Hash for file ' + + style(f'{file_name}.csr', fg='green') + + ' = ' + + style(f'{csr_hash}', fg='red')) + + if silent: echo(' Signing COLLABORATOR certificate') signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) write_crt(signed_col_cert, f'{cert_name}.crt') - register_collaborator(cert_dir_path / 'client' / f'{file_name}.crt') + register_collaborator(CERT_DIR / 'client' / f'{file_name}.crt') else: - echo(style('Not signing certificate.', fg='red') - + ' Please check with this collaborator to get the' - ' correct certificate for this federation.') - return - if len(common_name) == 0: - # If the collaborator name is provided, the collaborator and - # certificate does not need to be exported - return + if confirm('Do you want to sign this certificate?'): - # Remove unneeded CSR - remove(f'{cert_name}.csr') + echo(' Signing COLLABORATOR certificate') + signed_col_cert = sign_certificate(csr, signing_key, signing_crt.subject) + write_crt(signed_col_cert, f'{cert_name}.crt') + register_collaborator(CERT_DIR / 'client' / f'{file_name}.crt') - archive_type = 'zip' - archive_name = f'agg_to_{file_name}_signed_cert' + else: + echo(style('Not signing certificate.', fg='red') + + ' Please check with this collaborator to get the' + ' correct certificate for this federation.') + return - # Collaborator certificate signing request - tmp_dir = join(mkdtemp(), 'openfl', archive_name) + if len(common_name) == 0: + # If the collaborator name is provided, the collaborator and + # certificate does not need to be exported + return - Path(f'{tmp_dir}/client').mkdir(parents=True, exist_ok=True) - # Copy the signed cert to the temporary directory - copy(f'{cert_dir_path}/client/{file_name}.crt', f'{tmp_dir}/client/') - # Copy the CA certificate chain to the temporary directory - copy(f'{cert_dir_path}/cert_chain.crt', tmp_dir) + # Remove unneeded CSR + remove(f'{cert_name}.csr') - # Create Zip archive of directory - make_archive(archive_name, archive_type, tmp_dir) + archive_type = 'zip' + archive_name = f'agg_to_{file_name}_signed_cert' + + # Collaborator certificate signing request + tmp_dir = join(mkdtemp(), 'openfl', archive_name) + + Path(f'{tmp_dir}/client').mkdir(parents=True, exist_ok=True) + # Copy the signed cert to the temporary directory + copy(f'{CERT_DIR}/client/{file_name}.crt', f'{tmp_dir}/client/') + # Copy the CA certificate chain to the temporary directory + copy(f'{CERT_DIR}/cert_chain.crt', tmp_dir) + + # Create Zip archive of directory + make_archive(archive_name, archive_type, tmp_dir) - else: - # Copy the signed certificate and cert chain into PKI_DIR - previous_crts = glob(f'{cert_dir_path}/client/*.crt') - unpack_archive(import_, extract_dir=cert_dir_path) - updated_crts = glob(f'{cert_dir_path}/client/*.crt') - cert_difference = list(set(updated_crts) - set(previous_crts)) - if len(cert_difference) != 0: - crt = basename(cert_difference[0]) - echo(f'Certificate {crt} installed to PKI directory') else: - echo('Certificate updated in the PKI directory') + # Copy the signed certificate and cert chain into PKI_DIR + previous_crts = glob(f'{CERT_DIR}/client/*.crt') + unpack_archive(import_, extract_dir=CERT_DIR) + updated_crts = glob(f'{CERT_DIR}/client/*.crt') + cert_difference = list(set(updated_crts) - set(previous_crts)) + if len(cert_difference) != 0: + crt = basename(cert_difference[0]) + echo(f'Certificate {crt} installed to PKI directory') + else: + echo('Certificate updated in the PKI directory') @collaborator.command(name='uninstall-cert') @option('-c', '--cert_path', help='The cert path where pki certs reside', required=True) -def _uninstall_cert(cert_path): - uninstall_cert(cert_path) +@option('-k', '--key_path', + help='The key path where key reside', required=True) +def _uninstall_cert(cert_path, key_path): + uninstall_cert(cert_path, key_path) -def uninstall_cert(cert_path=None): +def uninstall_cert(cert_path=None, key_path=None): """Uninstall certs under a given directory.""" - import shutil + from openfl.utilities.utils import rmtree from pathlib import Path cert_path = Path(cert_path).absolute() - shutil.rmtree(cert_path, ignore_errors=True) + rmtree(cert_path, ignore_errors=True) + key_path = Path(key_path).absolute() + rmtree(key_path, ignore_errors=True) diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index b728b57eb5..c6105412fb 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -213,14 +213,18 @@ def import_(archive): @workspace.command(name='certify') +@option('-cdir', '--cert_dir', + help='The cert directory path where CA certs and keys will reside', required=False) @option('-c', '--cert_path', - help='The cert path where pki certs will reside', required=False) -def certify_(cert_path): + help='The cert path where CA signing cert will reside', required=False) +@option('-k', '--key_path', + help='The cert path where CA key path will reside', required=False) +def certify_(cert_dir, cert_path, key_path): """Create certificate authority for federation.""" - certify(cert_path) + certify(cert_dir, cert_path, key_path) -def certify(cert_path=None): +def certify(cert_dir=None, cert_path=None, key_path=None): """Create certificate authority for federation.""" from cryptography.hazmat.primitives import serialization @@ -234,10 +238,10 @@ def certify(cert_path=None): echo('1. Create Root CA') echo('1.1 Create Directories') - if cert_path: - cert_path = Path(cert_path).absolute() - (cert_path / 'cert').mkdir(parents=True, exist_ok=True) - cert_dir_path = cert_path / 'cert' + if cert_dir: + cert_dir = Path(cert_dir).absolute() + (cert_dir / 'cert').mkdir(parents=True, exist_ok=True) + cert_dir_path = cert_dir / 'cert' else: cert_dir_path = CERT_DIR @@ -298,42 +302,67 @@ def certify(cert_path=None): echo('2.3 Create Signing Certificate CSR') - signing_csr_path = 'ca/signing-ca.csr' - signing_crt_path = 'ca/signing-ca.crt' - signing_key_path = 'ca/signing-ca/private/signing-ca.key' - signing_private_key, signing_csr = generate_signing_csr() # Write Signing CA CSR to disk + signing_csr_path = 'ca/signing-ca.csr' with open(cert_dir_path / signing_csr_path, 'wb') as f: f.write(signing_csr.public_bytes( encoding=serialization.Encoding.PEM, )) - with open(cert_dir_path / signing_key_path, 'wb') as f: - f.write(signing_private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() - )) + if key_path: + key_path = Path(key_path).absolute() + signing_key_path = 'signing-ca.key' + with open(key_path / signing_key_path, 'wb') as f: + f.write(signing_private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) + else: + signing_key_path = 'ca/signing-ca/private/signing-ca.key' + with open(cert_dir_path / signing_key_path, 'wb') as f: + f.write(signing_private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + )) echo('2.4 Sign Signing Certificate CSR') signing_cert = sign_certificate(signing_csr, root_private_key, root_cert.subject, ca=True) - with open(cert_dir_path / signing_crt_path, 'wb') as f: - f.write(signing_cert.public_bytes( - encoding=serialization.Encoding.PEM, - )) + if cert_path: + cert_path = Path(cert_path).absolute() + signing_crt_path = 'signing-ca.crt' + with open(cert_path / signing_crt_path, 'wb') as f: + f.write(signing_cert.public_bytes( + encoding=serialization.Encoding.PEM, + )) + else: + signing_crt_path = 'ca/signing-ca.crt' + with open(cert_dir_path / signing_crt_path, 'wb') as f: + f.write(signing_cert.public_bytes( + encoding=serialization.Encoding.PEM, + )) echo('3 Create Certificate Chain') # create certificate chain file by combining root-ca and signing-ca - with open(cert_dir_path / 'cert_chain.crt', 'w', encoding='utf-8') as d: - with open(cert_dir_path / 'ca/root-ca.crt', encoding='utf-8') as s: - d.write(s.read()) - with open(cert_dir_path / 'ca/signing-ca.crt') as s: - d.write(s.read()) + if cert_path: + cert_chain_path = Path(cert_path).absolute() + with open(cert_chain_path / 'cert_chain.crt', 'w', encoding='utf-8') as d: + with open(cert_dir_path / 'ca/root-ca.crt', encoding='utf-8') as s: + d.write(s.read()) + with open(cert_chain_path / 'signing-ca.crt') as s: + d.write(s.read()) + else: + with open(cert_dir_path / 'cert_chain.crt', 'w', encoding='utf-8') as d: + with open(cert_dir_path / 'ca/root-ca.crt', encoding='utf-8') as s: + d.write(s.read()) + with open(cert_dir_path / 'ca/signing-ca.crt') as s: + d.write(s.read()) echo('\nDone.') @@ -538,17 +567,21 @@ def open_pipe(command: str): @workspace.command(name='uninstall-cert') @option('-c', '--cert_path', help='The cert path where pki certs reside', required=True) -def _uninstall_cert(cert_path): - uninstall_cert(cert_path) +@option('-k', '--key_path', + help='The key path where key reside', required=True) +def _uninstall_cert(cert_path, key_path): + uninstall_cert(cert_path, key_path) -def uninstall_cert(cert_path=None): +def uninstall_cert(cert_path=None, key_path=None): """Uninstall certs under a given directory.""" - import shutil + from openfl.utilities.utils import rmtree from pathlib import Path cert_path = Path(cert_path).absolute() - shutil.rmtree(cert_path, ignore_errors=True) + rmtree(cert_path, ignore_errors=True) + key_path = Path(key_path).absolute() + rmtree(key_path, ignore_errors=True) def apply_template_plan(prefix, template): diff --git a/tests/github/test_pki_cert_location.py b/tests/github/test_pki_cert_location.py index cfd7b72d3e..1af0a59c74 100644 --- a/tests/github/test_pki_cert_location.py +++ b/tests/github/test_pki_cert_location.py @@ -29,8 +29,11 @@ parser.add_argument('--col1-data-path', default='1') parser.add_argument('--col2-data-path', default='2') parser.add_argument('--agg-cert-path', default=Path.cwd()) + parser.add_argument('--agg-key-path', default=Path.cwd()) parser.add_argument('--col1-cert-path', default=Path.cwd()) + parser.add_argument('--col1-key-path', default=Path.cwd()) parser.add_argument('--col2-cert-path', default=Path.cwd()) + parser.add_argument('--col2-key-path', default=Path.cwd()) parser.add_argument('--save-model') origin_dir = Path.cwd().resolve() @@ -42,42 +45,57 @@ rounds_to_train = args.rounds_to_train col1, col2 = args.col1, args.col2 col1_data_path, col2_data_path = args.col1_data_path, args.col2_data_path + + os.makedirs(args.agg_cert_path, exist_ok=True) + os.makedirs(args.agg_key_path, exist_ok=True) + os.makedirs(args.col1_cert_path, exist_ok=True) + os.makedirs(args.col1_key_path, exist_ok=True) + os.makedirs(args.col2_cert_path, exist_ok=True) + os.makedirs(args.col2_key_path, exist_ok=True) + + ca_dir_path = Path(args.agg_cert_path).resolve() agg_cert_path = Path(args.agg_cert_path).resolve() + agg_key_path = Path(args.agg_key_path).resolve() col1_cert_path = Path(args.col1_cert_path).resolve() + col1_key_path = Path(args.col1_key_path).resolve() col2_cert_path = Path(args.col2_cert_path).resolve() + col2_key_path = Path(args.col2_key_path).resolve() save_model = args.save_model # START # ===== # Make sure you are in a Python virtual environment with the FL package installed. - create_certified_workspace(fed_workspace, template, fqdn, rounds_to_train, agg_cert_path) - certify_aggregator(fqdn, agg_cert_path) + create_certified_workspace(fed_workspace, template, fqdn, + rounds_to_train, ca_dir_path, agg_cert_path, agg_key_path) + certify_aggregator(fqdn, agg_cert_path, agg_key_path) workspace_root = Path().resolve() # Get the absolute directory path for the workspace # Create collaborator #1 create_collaborator(col1, workspace_root, col1_data_path, archive_name, - fed_workspace, col1_cert_path, agg_cert_path) + fed_workspace, col1_cert_path, col1_key_path, agg_cert_path, agg_key_path) # Create collaborator #2 create_collaborator(col2, workspace_root, col2_data_path, archive_name, - fed_workspace, col2_cert_path, agg_cert_path) + fed_workspace, col2_cert_path, col2_key_path, agg_cert_path, agg_key_path) # Run the federation with ProcessPoolExecutor(max_workers=3) as executor: executor.submit( - check_call, ['fx', 'aggregator', 'start', '-c', agg_cert_path], + check_call, ['fx', 'aggregator', 'start', '-c', agg_cert_path, '-k', agg_key_path], cwd=workspace_root) time.sleep(5) dir1 = workspace_root / col1 / fed_workspace executor.submit( - check_call, ['fx', 'collaborator', 'start', '-n', col1, '-c', col1_cert_path], + check_call, ['fx', 'collaborator', 'start', '-n', col1, + '-c', col1_cert_path, '-k', col1_key_path], cwd=dir1) dir2 = workspace_root / col2 / fed_workspace executor.submit( - check_call, ['fx', 'collaborator', 'start', '-n', col2, '-c', col2_cert_path], + check_call, ['fx', 'collaborator', 'start', '-n', col2, + '-c', col2_cert_path, '-k', col2_key_path], cwd=dir2) # Convert model to native format diff --git a/tests/github/utils.py b/tests/github/utils.py index f6a5a8be0e..7c85584026 100644 --- a/tests/github/utils.py +++ b/tests/github/utils.py @@ -9,7 +9,7 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_workspace, - cert_path=None, ca_cert_path=None): + cert_path=None, key_path=None, ca_cert_path=None, ca_key_path=None): # Copy workspace to collaborator directories (these can be on different machines) col_path = workspace_root / col shutil.rmtree(col_path, ignore_errors=True) # Remove any existing directory @@ -23,10 +23,10 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_worksp # Create collaborator certificate request # Remove '--silent' if you run this manually - if cert_path: + if cert_path and key_path: check_call( ['fx', 'collaborator', 'generate-cert-request', '-d', data_path, - '-n', col, '-c', cert_path, '--silent'], + '-n', col, '-c', cert_path, '-k', key_path, '--silent'], cwd=col_path / fed_workspace ) else: @@ -39,10 +39,10 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_worksp # Sign collaborator certificate # Remove '--silent' if you run this manually request_pkg = col_path / fed_workspace / f'col_{col}_to_agg_cert_request.zip' - if ca_cert_path: + if ca_cert_path and ca_key_path: check_call( ['fx', 'collaborator', 'certify', '--request-pkg', str(request_pkg), - '-c', ca_cert_path, '--silent'], + '-c', ca_cert_path, '-k', ca_key_path, '--silent'], cwd=workspace_root) else: check_call( @@ -51,9 +51,10 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_worksp # Import the signed certificate from the aggregator import_path = workspace_root / f'agg_to_col_{col}_signed_cert.zip' - if cert_path: + if cert_path and key_path: check_call( - ['fx', 'collaborator', 'certify', '--import', import_path, '-c', cert_path], + ['fx', 'collaborator', 'certify', '--import', import_path, + '-c', cert_path, '-k', key_path], cwd=col_path / fed_workspace ) else: @@ -63,7 +64,8 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_worksp ) -def create_certified_workspace(path, template, fqdn, rounds_to_train, cert_path=None): +def create_certified_workspace(path, template, fqdn, rounds_to_train, + cert_dir=None, cert_path=None, key_path=None): shutil.rmtree(path, ignore_errors=True) check_call(['fx', 'workspace', 'create', '--prefix', path, '--template', template]) os.chdir(path) @@ -83,8 +85,9 @@ def create_certified_workspace(path, template, fqdn, rounds_to_train, cert_path= except (ValueError, TypeError): pass # Create certificate authority for workspace - if cert_path: - check_call(['fx', 'workspace', 'certify', '-c', cert_path]) + if cert_dir and cert_path and key_path: + check_call(['fx', 'workspace', 'certify', + '-cdir', cert_dir, '-c', cert_path, '-k', key_path]) else: check_call(['fx', 'workspace', 'certify']) @@ -92,12 +95,14 @@ def create_certified_workspace(path, template, fqdn, rounds_to_train, cert_path= check_call(['fx', 'workspace', 'export']) -def certify_aggregator(fqdn, cert_path=None): - if cert_path: +def certify_aggregator(fqdn, cert_path=None, key_path=None): + if cert_path and key_path: # Create aggregator certificate - check_call(['fx', 'aggregator', 'generate-cert-request', '--fqdn', fqdn, '-c', cert_path]) + check_call(['fx', 'aggregator', 'generate-cert-request', '--fqdn', fqdn, + '-c', cert_path, '-k', key_path]) # Sign aggregator certificate - check_call(['fx', 'aggregator', 'certify', '--fqdn', fqdn, '-c', cert_path, '--silent']) + check_call(['fx', 'aggregator', 'certify', '--fqdn', fqdn, + '-c', cert_path, '-k', key_path, '--silent']) else: # Create aggregator certificate check_call(['fx', 'aggregator', 'generate-cert-request', '--fqdn', fqdn]) From a623350c87231b47ebd4f0c1be9fc4daaa3fbf87 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 13:59:18 -0800 Subject: [PATCH 09/19] remove tmp dir to resolve conflicts Signed-off-by: Mansi Sharma --- openfl/interface/collaborator.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 9dc62a24f5..c183978c0e 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -202,6 +202,8 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, from os import remove from glob import glob + from openfl.utilities.utils import rmtree + archive_type = 'zip' archive_name = f'col_{common_name}_to_agg_cert_request' archive_file_name = archive_name + '.' + archive_type @@ -223,6 +225,7 @@ def generate_cert_request(collaborator_name, data_path, silent, skip_package, # Create Zip archive of directory make_archive(archive_name, archive_type, tmp_dir) + rmtree(tmp_dir) echo(f'Archive {archive_file_name} with certificate signing' f' request created') @@ -327,6 +330,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, from openfl.cryptography.io import read_key from openfl.cryptography.io import write_crt from openfl.interface.cli_helper import CERT_DIR + from openfl.utilities.utils import rmtree common_name = f'{collaborator_name}'.lower() @@ -430,6 +434,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, # Create Zip archive of directory make_archive(archive_name, archive_type, tmp_dir) + rmtree(tmp_dir) else: # Copy the signed certificate and cert chain into PKI_DIR @@ -536,6 +541,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, # Create Zip archive of directory make_archive(archive_name, archive_type, tmp_dir) + rmtree(tmp_dir) else: # Copy the signed certificate and cert chain into PKI_DIR From e21a6665843456aea897a8a2c6eecbe9e4ecd9b7 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 14:00:15 -0800 Subject: [PATCH 10/19] remove tmp dir to resolve conflicts Signed-off-by: Mansi Sharma --- openfl/interface/collaborator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index c183978c0e..773017ce51 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -330,6 +330,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, from openfl.cryptography.io import read_key from openfl.cryptography.io import write_crt from openfl.interface.cli_helper import CERT_DIR + from openfl.utilities.utils import rmtree common_name = f'{collaborator_name}'.lower() From 898e9b72f70bc43805e309c45cb332896bd7106f Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 14:20:24 -0800 Subject: [PATCH 11/19] fix unistall certs Signed-off-by: Mansi Sharma --- openfl/interface/aggregator.py | 2 +- openfl/interface/collaborator.py | 2 +- openfl/interface/workspace.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 7358e5efbd..1f4f5459cb 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -310,6 +310,6 @@ def uninstall_cert(cert_path=None, key_path=None): from pathlib import Path cert_path = Path(cert_path).absolute() - rmtree(cert_path, ignore_errors=True) key_path = Path(key_path).absolute() + rmtree(cert_path, ignore_errors=True) rmtree(key_path, ignore_errors=True) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index a27999ecb1..e154ab3815 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -571,6 +571,6 @@ def uninstall_cert(cert_path=None, key_path=None): from pathlib import Path cert_path = Path(cert_path).absolute() - rmtree(cert_path, ignore_errors=True) key_path = Path(key_path).absolute() + rmtree(cert_path, ignore_errors=True) rmtree(key_path, ignore_errors=True) diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index 69c7e1fa0b..e0557f588e 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -589,8 +589,8 @@ def uninstall_cert(cert_path=None, key_path=None): from pathlib import Path cert_path = Path(cert_path).absolute() - rmtree(cert_path, ignore_errors=True) key_path = Path(key_path).absolute() + rmtree(cert_path, ignore_errors=True) rmtree(key_path, ignore_errors=True) From 8c7ae9134ee71612dd638c4391c1b84ab574876b Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 14:36:35 -0800 Subject: [PATCH 12/19] fixing indentation after rebase Signed-off-by: Mansi Sharma --- openfl/interface/collaborator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index e154ab3815..c4a429793d 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -539,9 +539,9 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, # Copy the CA certificate chain to the temporary directory copy(f'{CERT_DIR}/cert_chain.crt', tmp_dir) - # Create Zip archive of directory - make_archive(archive_name, archive_type, tmp_dir) - rmtree(tmp_dir) + # Create Zip archive of directory + make_archive(archive_name, archive_type, tmp_dir) + rmtree(tmp_dir) else: # Copy the signed certificate and cert chain into PKI_DIR From 4711e08b15e43b5ceea78a608eaa9e71f4f26b6d Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 14:51:08 -0800 Subject: [PATCH 13/19] remove unwanted comments Signed-off-by: Mansi Sharma --- openfl/interface/collaborator.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index c4a429793d..1311d314a9 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -342,7 +342,6 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, if request_pkg: unpack_archive(request_pkg, extract_dir=cert_path) csr = glob(f'{cert_path}/*.csr')[0] - print("csr is", csr) else: if collaborator_name is None: echo('collaborator_name can only be omitted if signing\n' @@ -352,12 +351,9 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, 'col_one_to_agg_cert_request.zip') return csr = glob(f'{cert_path}/col_{common_name}.csr')[0] - print("Csr is", csr) copy(csr, cert_path) cert_name = splitext(csr)[0] - print("cert name", cert_name) file_name = basename(cert_name) - print("file name", file_name) signing_key_path = 'signing-ca.key' signing_crt_path = 'signing-ca.crt' From 22403469032eaf9e0b6378e5a13e901e2e7fc675 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 15:31:27 -0800 Subject: [PATCH 14/19] fixing csr name Signed-off-by: Mansi Sharma --- openfl/interface/collaborator.py | 2 +- tests/github/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 1311d314a9..29dc90f16c 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -341,7 +341,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, if not import_: if request_pkg: unpack_archive(request_pkg, extract_dir=cert_path) - csr = glob(f'{cert_path}/*.csr')[0] + csr = glob(f'{cert_path}/col_{common_name}.csr')[0] else: if collaborator_name is None: echo('collaborator_name can only be omitted if signing\n' diff --git a/tests/github/utils.py b/tests/github/utils.py index 7c85584026..d7a68a37df 100644 --- a/tests/github/utils.py +++ b/tests/github/utils.py @@ -42,7 +42,7 @@ def create_collaborator(col, workspace_root, data_path, archive_name, fed_worksp if ca_cert_path and ca_key_path: check_call( ['fx', 'collaborator', 'certify', '--request-pkg', str(request_pkg), - '-c', ca_cert_path, '-k', ca_key_path, '--silent'], + '-n', col, '-c', ca_cert_path, '-k', ca_key_path, '--silent'], cwd=workspace_root) else: check_call( From bd1f53a665efab0759b022388cb7c1fbca9c8886 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Thu, 12 Jan 2023 16:10:01 -0800 Subject: [PATCH 15/19] fix documentation Signed-off-by: Mansi Sharma --- docs/running_the_federation.rst | 81 +++++++++++++------------- tests/github/test_pki_cert_location.py | 6 +- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/docs/running_the_federation.rst b/docs/running_the_federation.rst index cb40641482..572194ce74 100644 --- a/docs/running_the_federation.rst +++ b/docs/running_the_federation.rst @@ -956,9 +956,10 @@ Setting Up the Certificate Authority .. code-block:: console - fx workspace certify -c CERT_PATH + fx workspace certify -c CERT_PATH -k KEY_PATH -cdir CERT_DIR - where :code:`CERT_PATH` is the path where the certificates will be stored for this node. + where :code:`CERT_PATH` is the path where the CA signing certificate and the certificate chain will be stored for this node, + :code:`KEY_PATH` is where the CA signing key path will be stored and :code:`CERT_DIR` is the directory where rest of CA certificates (e.g. root CA cert and key) will reside. 3. Run the aggregator certificate creation command, replacing :code:`AFQDN` with the actual `fully qualified domain name (FQDN) `_ for the aggregator node. @@ -966,11 +967,11 @@ Setting Up the Certificate Authority fx aggregator generate-cert-request --fqdn AFQDN - To store certificates under :code:`CERT_PATH`: + To store certificate under :code:`CERT_PATH` and key under :code:`KEY_PATH`: .. code-block:: console - fx aggregator generate-cert-request --fqdn AFQDN -c CERT_PATH + fx aggregator generate-cert-request --fqdn AFQDN -c CERT_PATH -k KEY_PATH .. note:: @@ -1000,11 +1001,11 @@ Setting Up the Certificate Authority fx aggregator certify --fqdn AFQDN - If :code:`CERT_PATH` was used to store CA signing certificates, specify the same path here: + If :code:`CERT_PATH` and :code:`KEY_PATH` was used to store CA signing certificate and signing key respectively, specify the same path here: .. code-block:: console - fx aggregator certify --fqdn AFQDN -c CERT_PATH + fx aggregator certify --fqdn AFQDN -c CERT_PATH -k KEY_PATH .. note:: @@ -1016,17 +1017,17 @@ Setting Up the Certificate Authority 5. This node now has a signed security certificate as the aggregator for this new federation. You should have the following files. - +---------------------------+--------------------------------------------------+ - | File Type | Filename | - +===========================+==================================================+ - | Certificate chain | CERT.PATH/cert/cert_chain.crt | - +---------------------------+--------------------------------------------------+ - | Aggregator certificate | CERT.PATH/cert/server/agg_{AFQDN}.crt | - +---------------------------+--------------------------------------------------+ - | Aggregator key | CERT.PATH/cert/server/agg_{AFQDN}.key | - +---------------------------+--------------------------------------------------+ + +---------------------------+-----------------------------------------------------------------------------+ + | File Type | Filename | + +===========================+=============================================================================+ + | Certificate chain | WORKSPACE.PATH/cert/cert_chain.crt or CERT.PATH/cert_chain.crt | + +---------------------------+-----------------------------------------------------------------------------+ + | Aggregator certificate | WORKSPACE.PATH/cert/server/agg_{AFQDN}.crt or CERT.PATH/agg_{AFQDN}.crt | + +---------------------------+-----------------------------------------------------------------------------+ + | Aggregator key | WORKSPACE.PATH/cert/server/agg_{AFQDN}.key or KEY.PATH/agg_{AFQDN}.key | + +---------------------------+-----------------------------------------------------------------------------+ - where **CERT.PATH** is :code:`WORKSPACE.PATH` by default or the path specified by the user and **AFQDN** is the fully-qualified domain name of the aggregator node. + where **AFQDN** is the fully-qualified domain name of the aggregator node. .. _workspace_export: @@ -1068,29 +1069,27 @@ Importing the Workspace fx collaborator generate-cert-request -n {COL_LABEL} - To store certs under :code:`CERT_PATH_COL/cert` other than :code:`WORKSPACE_PATH/cert`: + To store certs under :code:`CERT_PATH_COL/` and key under :code:`KEY_PATH_COL/` other than :code:`WORKSPACE_PATH/cert`: .. code-block:: console - fx collaborator generate-cert-request -n {COL_LABEL} -c {CERT_PATH_COL} + fx collaborator generate-cert-request -n {COL_LABEL} -c {CERT_PATH_COL} -k {KEY_PATH_COL} - where **CERT_PATH_COL** is the path where collaborator certificates (client) will be stored. + where **CERT_PATH_COL** is the path where collaborator certificates (client) will be stored and **KEY_PATH_COL** is where the collaborator key is stored. The creation script will also ask you to specify the path to the data. For this example, enter the integer that represents which MNIST shard to use on this collaborator node. For the first collaborator node enter **1**. For the second collaborator node enter **2**. This will create the following files: - +-----------------------------+--------------------------------------------------------+ - | File Type | Filename | - +=============================+========================================================+ - | Collaborator CSR | CERT.PATH.COL/cert/client/col_{COL_LABEL}.csr | - +-----------------------------+--------------------------------------------------------+ - | Collaborator key | CERT.PATH.COL/cert/client/col_{COL_LABEL}.key | - +-----------------------------+--------------------------------------------------------+ - | Collaborator CSR Package | WORKSPACE.PATH/col_{COL_LABEL}_to_agg_cert_request.zip | - +-----------------------------+--------------------------------------------------------+ - - where **CERT.PATH.COL** is :code:`WORKSPACE.PATH` by default or the path specified by the user. + +-----------------------------+-------------------------------------------------------------------------------------+ + | File Type | Filename | + +=============================+=====================================================================================+ + | Collaborator CSR | WORKSPACE.PATH/cert/client/col_{COL_LABEL}.csr or CERT.PATH.COL/col_{COL_LABEL}.csr | + +-----------------------------+-------------------------------------------------------------------------------------+ + | Collaborator key | WORKSPACE.PATH/cert/client/col_{COL_LABEL}.key or KEY.PATH.COL/col_{COL_LABEL}.key | + +-----------------------------+-------------------------------------------------------------------------------------+ + | Collaborator CSR Package | WORKSPACE.PATH/col_{COL_LABEL}_to_agg_cert_request.zip | + +-----------------------------+-------------------------------------------------------------------------------------+ 4. On the aggregator node (i.e., the certificate authority in this example), sign the Collaborator CSR Package from the collaborator nodes. @@ -1100,11 +1099,11 @@ Importing the Workspace where :code:`/PATH/TO/col_{COL_LABEL}_to_agg_cert_request.zip` is the path to the Collaborator CSR Package containing the :code:`.csr` file from the collaborator node. The certificate authority will sign this certificate for use in the federation. - If :code:`CERT_PATH` was used at the aggregator node to store CA signing certificates, specify the same path here: + If :code:`CERT_PATH` and :code:`KEY_PATH` was used at the aggregator node to store CA signing certificate and signing key, specify the same path here: .. code-block:: console - fx collaborator certify --request-pkg /PATH/TO/col_{COL_LABEL}_to_agg_cert_request.zip -c CERT_PATH + fx collaborator certify --request-pkg /PATH/TO/col_{COL_LABEL}_to_agg_cert_request.zip -n collaborator_name -c CERT_PATH -k KEY_PATH The command packages the signed collaborator certificate, along with the **cert_chain.crt** file needed to verify certificate signatures, for transport back to the collaborator node: @@ -1120,11 +1119,11 @@ Importing the Workspace fx collaborator certify --import /PATH/TO/agg_to_col_{COL_LABEL}_signed_cert.zip - If :code:`CERT_PATH_COL` was used to store collaborator certificates for this node, specify the collaborator certificate path here: + If :code:`CERT_PATH_COL` and :code:`KEY_PATH_COL` was used to store collaborator certificate and key for this node, specify the paths here: .. code-block:: console - fx collaborator certify --import /PATH/TO/agg_to_col_{COL_LABEL}_signed_cert.zip -c CERT_PATH_COL + fx collaborator certify --import /PATH/TO/agg_to_col_{COL_LABEL}_signed_cert.zip -c CERT_PATH_COL -k KEY_PATH_COL .. _running_the_federation.start_nodes: @@ -1141,11 +1140,11 @@ STEP 3: Start the Federation fx aggregator start - If :code:`CERT_PATH` was used to store certificates for this node, specify the same path here: + If :code:`CERT_PATH` and :code:`KEY_PATH` was used to store certificates for this node, specify the same path here: .. code-block:: console - fx aggregator start -c ${CERT_PATH} + fx aggregator start -c ${CERT_PATH} -k ${KEY_PATH} Now, the Aggregator is running and waiting for Collaborators to connect. @@ -1163,11 +1162,11 @@ STEP 3: Start the Federation where :code:`COLLABORATOR_LABEL` is the label for this Collaborator. - If :code:`CERT_PATH_COL` was used to store certificates for this node, specify the same path here: + If :code:`CERT_PATH_COL` and :code:`KEY_PATH_COL` was used to store certificates for this node, specify the same path here: .. code-block:: console - fx collaborator start -n {COLLABORATOR_LABEL} -c ${CERT_PATH_COL} + fx collaborator start -n {COLLABORATOR_LABEL} -c ${CERT_PATH_COL} -k ${KEY_PATH_COL} .. note:: @@ -1214,9 +1213,9 @@ If :code:`CERT_PATH` was used to store certificates for any node, uninstall them .. code-block:: console - fx workspace uninstall-cert -c ${CERT_PATH} - fx aggregator uninstall-cert -c ${CERT_PATH} - fx collaborator uninstall-cert -c ${CERT_PATH_COL} + fx workspace uninstall-cert -c ${CERT_PATH} -k ${KEY_PATH} + fx aggregator uninstall-cert -c ${CERT_PATH} -k ${KEY_PATH} + fx collaborator uninstall-cert -c ${CERT_PATH_COL} -k ${KEY_PATH_COL} .. _running_the_federation_docker: diff --git a/tests/github/test_pki_cert_location.py b/tests/github/test_pki_cert_location.py index 1af0a59c74..fd6ae87559 100644 --- a/tests/github/test_pki_cert_location.py +++ b/tests/github/test_pki_cert_location.py @@ -106,13 +106,13 @@ # Clear cert paths check_call( - ['fx', 'aggregator', 'uninstall-cert', '-c', agg_cert_path], + ['fx', 'aggregator', 'uninstall-cert', '-c', agg_cert_path, '-k', agg_key_path], cwd=workspace_root) check_call( - ['fx', 'collaborator', 'uninstall-cert', '-c', col1_cert_path], + ['fx', 'collaborator', 'uninstall-cert', '-c', col1_cert_path, '-k', col1_key_path], cwd=workspace_root / col1 / fed_workspace) check_call( - ['fx', 'collaborator', 'uninstall-cert', '-c', col2_cert_path], + ['fx', 'collaborator', 'uninstall-cert', '-c', col2_cert_path, '-k', col2_key_path], cwd=workspace_root / col2 / fed_workspace) os.chdir(origin_dir) rmtree(workspace_root) From ab5dfe6fef739691540a9631b2cebcadb8bf4c8c Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Wed, 18 Jan 2023 16:47:01 -0800 Subject: [PATCH 16/19] fix key paths Signed-off-by: Mansi Sharma --- openfl/interface/aggregator.py | 4 ++-- openfl/interface/collaborator.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 1f4f5459cb..29b1041c55 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -68,8 +68,8 @@ def start_(plan, authorized_cols, secure, cert_path, key_path): common_name = plan.config['network']['settings']['agg_addr'].lower() plan.get_server(root_certificate=f'{cert_path}/cert_chain.crt', - private_key=f'{cert_path}/agg_{common_name}.key', - certificate=f'{key_path}/agg_{common_name}.crt').serve() + private_key=f'{key_path}/agg_{common_name}.key', + certificate=f'{cert_path}/agg_{common_name}.crt').serve() else: plan.get_server().serve() diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index 29dc90f16c..dfc514d3d1 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -72,7 +72,7 @@ def start_(plan, collaborator_name, data_config, secure, cert_path, key_path): common_name = f'{collaborator_name}'.lower() plan.get_collaborator(collaborator_name, root_certificate=f'{cert_path}/cert_chain.crt', - private_key=f'{cert_path}/col_{common_name}.key', + private_key=f'{key_path}/col_{common_name}.key', certificate=f'{cert_path}/col_{common_name}.crt').run() else: plan.get_collaborator(collaborator_name).run() From c3b2ee88fc37d616c628085d204fc64ee43fb75c Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Wed, 18 Jan 2023 16:48:04 -0800 Subject: [PATCH 17/19] changing default certs path Signed-off-by: Mansi Sharma --- openfl/federated/plan/plan.py | 16 ++++++++++------ openfl/interface/cli_helper.py | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/openfl/federated/plan/plan.py b/openfl/federated/plan/plan.py index fbafb6a449..7fb24c3edc 100644 --- a/openfl/federated/plan/plan.py +++ b/openfl/federated/plan/plan.py @@ -498,11 +498,13 @@ def get_collaborator(self, collaborator_name, root_certificate=None, private_key def get_client(self, collaborator_name, aggregator_uuid, federation_uuid, root_certificate=None, private_key=None, certificate=None): """Get gRPC client for the specified collaborator.""" + from openfl.interface.cli_helper import CERT_DIR + common_name = collaborator_name if not root_certificate or not private_key or not certificate: - root_certificate = 'cert/cert_chain.crt' - certificate = f'cert/client/col_{common_name}.crt' - private_key = f'cert/client/col_{common_name}.key' + root_certificate = f'{CERT_DIR}/cert_chain.crt' + certificate = f'{CERT_DIR}/client/col_{common_name}.crt' + private_key = f'{CERT_DIR}/client/col_{common_name}.key' client_args = self.config['network'][SETTINGS] @@ -522,12 +524,14 @@ def get_client(self, collaborator_name, aggregator_uuid, federation_uuid, def get_server(self, root_certificate=None, private_key=None, certificate=None, **kwargs): """Get gRPC server of the aggregator instance.""" + from openfl.interface.cli_helper import CERT_DIR + common_name = self.config['network'][SETTINGS]['agg_addr'].lower() if not root_certificate or not private_key or not certificate: - root_certificate = 'cert/cert_chain.crt' - certificate = f'cert/server/agg_{common_name}.crt' - private_key = f'cert/server/agg_{common_name}.key' + root_certificate = f'{CERT_DIR}/cert_chain.crt' + certificate = f'{CERT_DIR}/server/agg_{common_name}.crt' + private_key = f'{CERT_DIR}/server/agg_{common_name}.key' server_args = self.config['network'][SETTINGS] diff --git a/openfl/interface/cli_helper.py b/openfl/interface/cli_helper.py index df304a4d36..3ecc5bda9f 100644 --- a/openfl/interface/cli_helper.py +++ b/openfl/interface/cli_helper.py @@ -19,7 +19,7 @@ WORKSPACE = SITEPACKS / 'openfl-workspace' TUTORIALS = SITEPACKS / 'openfl-tutorials' OPENFL_USERDIR = Path.home() / '.openfl' -CERT_DIR = Path('cert').absolute() +CERT_DIR = OPENFL_USERDIR / 'cert' def pretty(o): From 395d1dccc049ed9cf6bbfcc81aae2fe088d9b343 Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Tue, 31 Jan 2023 15:51:38 -0800 Subject: [PATCH 18/19] participants selection Signed-off-by: Mansi Sharma --- docs/running_the_federation.rst | 17 ++++++++++++ openfl/federated/plan/plan.py | 2 +- openfl/interface/aggregator.py | 1 + openfl/interface/collaborator.py | 1 + openfl/interface/workspace.py | 46 ++++++++++++++++++++++++++++++++ tests/github/utils.py | 3 ++- 6 files changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/running_the_federation.rst b/docs/running_the_federation.rst index 572194ce74..28ce074a97 100644 --- a/docs/running_the_federation.rst +++ b/docs/running_the_federation.rst @@ -801,6 +801,7 @@ However, continue with the following procedure for details in creating a federat - Ensures each node in the federation has a valid public key infrastructure (PKI) certificate. - Distributes the workspace from the aggregator node to the other collaborator nodes. + - Optionally, select a subset of registered/certified collaborators to participate in the federation. By default, all certified collaborators will be added. `STEP 3: Start the Federation`_ @@ -1125,6 +1126,22 @@ Importing the Workspace fx collaborator certify --import /PATH/TO/agg_to_col_{COL_LABEL}_signed_cert.zip -c CERT_PATH_COL -k KEY_PATH_COL +.. _select_participants: + +**On the Aggregator Node:** + +OPTIONAL STEP: Users can select participants to take part in a federation. By default, all the participants (Collaborators) certified by the CA are added to the plan :code:`plan/cols.yaml` file. To select only a subset of participants out of the displayed names of all certified collaborators for the current federation: + + .. code-block:: console + + fx workspace participants + +If :code:`CERT_PATH` was used at the aggregator node to store CA signing certificate, specify the same path here: + + .. code-block:: console + + fx workspace participants -c CERT_PATH + .. _running_the_federation.start_nodes: diff --git a/openfl/federated/plan/plan.py b/openfl/federated/plan/plan.py index 7fb24c3edc..a5c51b945a 100644 --- a/openfl/federated/plan/plan.py +++ b/openfl/federated/plan/plan.py @@ -499,7 +499,7 @@ def get_client(self, collaborator_name, aggregator_uuid, federation_uuid, root_certificate=None, private_key=None, certificate=None): """Get gRPC client for the specified collaborator.""" from openfl.interface.cli_helper import CERT_DIR - + common_name = collaborator_name if not root_certificate or not private_key or not certificate: root_certificate = f'{CERT_DIR}/cert_chain.crt' diff --git a/openfl/interface/aggregator.py b/openfl/interface/aggregator.py index 29b1041c55..5076187164 100644 --- a/openfl/interface/aggregator.py +++ b/openfl/interface/aggregator.py @@ -301,6 +301,7 @@ def certify(fqdn, silent, cert_path=None, key_path=None): @option('-k', '--key_path', help='The key path where key reside', required=True) def _uninstall_cert(cert_path, key_path): + """Uninstall cert/key pair under a given directory.""" uninstall_cert(cert_path, key_path) diff --git a/openfl/interface/collaborator.py b/openfl/interface/collaborator.py index dfc514d3d1..10144b8a41 100644 --- a/openfl/interface/collaborator.py +++ b/openfl/interface/collaborator.py @@ -558,6 +558,7 @@ def certify(collaborator_name, silent, request_pkg=None, import_=False, @option('-k', '--key_path', help='The key path where key reside', required=True) def _uninstall_cert(cert_path, key_path): + """Uninstall cert/key pair under a given directory.""" uninstall_cert(cert_path, key_path) diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index e0557f588e..510eb53855 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -574,12 +574,58 @@ def open_pipe(command: str): echo(f'\n ✔️ The image saved to file: {tag}.tar.gz') +@workspace.command(name='participants') +@option('-c', '--cert_path', + help='Cert path where signing CA certs reside', required=False) +def participants_(cert_path=None): + """Select a subset of registered collaborators for the experiment.""" + participants(cert_path) + + +def participants(cert_path=None): + """Select a subset of collaborators from all regsitered collaborators.""" + from click import Choice + from click import confirm + from click import prompt as click_prompt + from glob import glob + from yaml import dump + from pathlib import Path + from openfl.interface.cli_helper import CERT_DIR + + if cert_path: + cert_path = Path(cert_path).absolute() + participant_certs = glob(f'{cert_path}/col_*.crt') + else: + participant_certs = glob(f'{CERT_DIR}/client/col_*.crt') + + total_participants = [] + for cert in participant_certs: + total_participants.append(os.path.basename(os.path.normpath(cert)).split('.')[0][4:]) + + collaborators_added = [] + collaborators_to_add = True + + while collaborators_to_add: + collaborator = click_prompt('Select collaborator for the experiment: ', + type=Choice(total_participants), show_choices=True) + collaborators_added.append(collaborator) + total_participants.remove(collaborator) + + if not confirm("Are there additional collaborators to add to the experiment?"): + collaborators_to_add = False + + cols_file = Path('plan/cols.yaml').absolute() + with open(cols_file, 'w', encoding='utf-8') as f: + dump({'collaborators': collaborators_added}, f) + + @workspace.command(name='uninstall-cert') @option('-c', '--cert_path', help='The cert path where pki certs reside', required=True) @option('-k', '--key_path', help='The key path where key reside', required=True) def _uninstall_cert(cert_path, key_path): + """Uninstall cert/key pair under a given directory.""" uninstall_cert(cert_path, key_path) diff --git a/tests/github/utils.py b/tests/github/utils.py index d7a68a37df..4f1df92819 100644 --- a/tests/github/utils.py +++ b/tests/github/utils.py @@ -115,6 +115,7 @@ def create_signed_cert_for_collaborator(col, data_path): We do certs exchage for all participants in a single workspace to speed up this test run. Do not do this in real experiments in untrusted environments ''' + from openfl.interface.cli_helper import CERT_DIR print(f'Certifying collaborator {col} with data path {data_path}...') # Create collaborator certificate request check_call([ @@ -133,7 +134,7 @@ def create_signed_cert_for_collaborator(col, data_path): # Pack the collaborators private key and the signed cert # as well as it's data.yaml to a tarball tarfiles = ['plan/data.yaml', f'agg_to_col_{col}_signed_cert.zip'] - with os.scandir('cert/client') as iterator: + with os.scandir(f'{CERT_DIR}/client') as iterator: for entry in iterator: if entry.name.endswith('key'): tarfiles.append(entry.path) From 41039eb2fdb25e0685e5e8cd490ef12a365e4dee Mon Sep 17 00:00:00 2001 From: Mansi Sharma Date: Mon, 13 Feb 2023 11:58:05 -0800 Subject: [PATCH 19/19] adding support for uncertified participants Signed-off-by: Mansi Sharma --- docs/running_the_federation.rst | 5 ++++ openfl/interface/workspace.py | 43 +++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/docs/running_the_federation.rst b/docs/running_the_federation.rst index 28ce074a97..8987e129fc 100644 --- a/docs/running_the_federation.rst +++ b/docs/running_the_federation.rst @@ -1142,6 +1142,11 @@ If :code:`CERT_PATH` was used at the aggregator node to store CA signing certifi fx workspace participants -c CERT_PATH +If users want to add uncertified participants, or manually want to edit the collaborator names for the experiment: + + .. code-block:: console + + fx workspace participants --no_cert .. _running_the_federation.start_nodes: diff --git a/openfl/interface/workspace.py b/openfl/interface/workspace.py index 510eb53855..409a3b62a6 100644 --- a/openfl/interface/workspace.py +++ b/openfl/interface/workspace.py @@ -577,15 +577,17 @@ def open_pipe(command: str): @workspace.command(name='participants') @option('-c', '--cert_path', help='Cert path where signing CA certs reside', required=False) -def participants_(cert_path=None): +@option('--no_cert', help='Add uncertified participants to plan', is_flag=True) +def participants_(cert_path=None, no_cert=False): """Select a subset of registered collaborators for the experiment.""" - participants(cert_path) + participants(cert_path, no_cert) -def participants(cert_path=None): +def participants(cert_path=None, no_cert=False): """Select a subset of collaborators from all regsitered collaborators.""" from click import Choice from click import confirm + from click import edit from click import prompt as click_prompt from glob import glob from yaml import dump @@ -598,25 +600,30 @@ def participants(cert_path=None): else: participant_certs = glob(f'{CERT_DIR}/client/col_*.crt') - total_participants = [] - for cert in participant_certs: - total_participants.append(os.path.basename(os.path.normpath(cert)).split('.')[0][4:]) + if participant_certs: + total_participants = [] + for cert in participant_certs: + total_participants.append(os.path.basename(os.path.normpath(cert)).split('.')[0][4:]) - collaborators_added = [] - collaborators_to_add = True + collaborators_added = [] + collaborators_to_add = True - while collaborators_to_add: - collaborator = click_prompt('Select collaborator for the experiment: ', - type=Choice(total_participants), show_choices=True) - collaborators_added.append(collaborator) - total_participants.remove(collaborator) + while collaborators_to_add: + collaborator = click_prompt('Select collaborator for the experiment: ', + type=Choice(total_participants), show_choices=True) + collaborators_added.append(collaborator) + total_participants.remove(collaborator) - if not confirm("Are there additional collaborators to add to the experiment?"): - collaborators_to_add = False + if not confirm('Are there additional collaborators to add to the experiment?'): + collaborators_to_add = False - cols_file = Path('plan/cols.yaml').absolute() - with open(cols_file, 'w', encoding='utf-8') as f: - dump({'collaborators': collaborators_added}, f) + cols_file = Path('plan/cols.yaml').absolute() + with open(cols_file, 'w', encoding='utf-8') as f: + dump({'collaborators': collaborators_added}, f) + + if no_cert: + if confirm('Do you want to add participants (- participant_name) to the experiment?'): + edit(filename='plan/cols.yaml') @workspace.command(name='uninstall-cert')