Skip to content

Commit

Permalink
site-specific security example mostly working except for keycloak one
Browse files Browse the repository at this point in the history
  • Loading branch information
chesterxgchen committed Feb 6, 2025
1 parent 520c9f6 commit 3804309
Show file tree
Hide file tree
Showing 36 changed files with 1,490 additions and 561 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
"leveraging confidential computing's VM-based trust execution environment (TEE), NVIDIA FLARE will enable end-to-end confidential federated AI. We will brief touch on it in this chapter. The details will be added in the future. \n",
"\n",
"\n",
"* **Communication Security**\n",
"\n",
"Use of Secure Protocols – TLS for secure transmission. FLARE support both mutual TLS (mTLS) as well normal TLS with signed message \n",
"\n",
"\n",
"\n",
"\n",
"\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
"\n",
"## Site Policy Management\n",
"\n",
" see [section 6.3](../06.3_site_security/site_specific_security.ipynb)\n",
"\n",
" see [section 6.3](../06.3_site_security/site_specific_security.ipynb) for local site-specific authentication and authorization as well local site-specific security and privacy polcies. \n",
" \n",
"## Communication Security\n",
" see TODO \n",
"\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"\n",
"## Authentication\n",
"\n",
"NVFLARE’s authentication model is based on Public Key Infrastructure (PKI) technology:\n",
"NVFLARE’s authentication model is based on Public Key Infrastructure (PKI) technology\n",
"\n",
"For the FL project, the Project Admin uses the Provisioning Tool to create a Root CA with a self-signed root certificate. This Root CA will be used to issue all other certs needed by communicating parties.\n",
"\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"path": "admin_auth.AdminAuth",
"args": {
"orgs": {
"site-a": "http://localhost:8080/realms/myrealm/protocol/openid-connect/token"
"site-1": "http://localhost:8080/realms/myrealm/protocol/openid-connect/token"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import os

from src.fedavg import FedAvg
from src.network import SimpleNetwork

from nvflare.job_config.api import FedJob
from nvflare.job_config.script_runner import ScriptRunner

if __name__ == "__main__":
num_clients = 2
num_rounds = 2
job_name = "fedavg"
train_script = "src/client.py"
config_dir = "/tmp/nvflare/jobs/workdir"


job = FedJob(name = job_name, min_clients = num_clients)
controller = FedAvg(
stop_cond = "accuracy > 25",
save_filename = "global_model.pt",
initial_model = SimpleNetwork(),
num_clients = num_clients,
num_rounds = num_rounds,
)

job.to_server(controller)

# Add clients
for i in range(num_clients):
executor = ScriptRunner(script=train_script, script_args="")
job.to(executor, f"site-{i+1}")

job_config_dir = os.path.join(config_dir, job_name)
print(f"job-config for {job_name} is at ",job_config_dir)
job.export_job(config_dir)
# job.simulator_run(config_dir)


Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import json
import sys
import os


def get_security_handler() -> dict:
return json.loads(
"""
{
"id": "security_handler",
"path": "keycloak_security_handler.CustomSecurityHandler"
}
"""
)


def add_components_to_json(
input_file_path, output_file_path, site: str, receiving: bool = False, streaming_to_server: bool = False
):
try:
with open(input_file_path, "r") as file:
data = json.load(file)
except (FileNotFoundError, json.JSONDecodeError):
print(f"Error: Unable to read or parse JSON file: {input_file_path}")
return

new_components = [get_security_handler()]

# Append new components to the list
data["components"].extend(new_components)

# Write the updated JSON back to the file
with open(output_file_path, "w") as file:
json.dump(data, file, indent=4)

print(f"Successfully generate file: '{output_file_path}'.")


if __name__ == "__main__":

site_name = sys.argv[1]
project_root_dir = sys.argv[2]


print(site_name, project_root_dir)

input_file_path = os.path.join(project_root_dir, site_name, "local", "resources.json.default")
output_file_path = os.path.join(project_root_dir, site_name, "local", "resources.json")

add_components_to_json(input_file_path, output_file_path, site_name)
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import requests
import os
import sys

def save_access_token(access_token:str, destination_path):

# Ensure the destination directory exists
os.makedirs(os.path.dirname(destination_path), exist_ok=True)

with open(destination_path, "w") as f:
f.write(access_token)
print(f"Access token saved to {destination_path}")


def get_keycloak_acces_token(username, password, client_id,keycloak_url) -> str:


# Request payload
data = {
"username": username,
"password": password,
"grant_type": "password",
"client_id": client_id
}

try:
# Make a POST request to get the access token
response = requests.post(keycloak_url, data=data, headers={"Content-Type": "application/x-www-form-urlencoded"})
response_data = response.json()

# Extract the access token
access_token = response_data.get("access_token")

if not access_token:
print("Failed to retrieve access token.")
else:
return access_token

except Exception as e:
print(f"Error fetching access token: {e}")


if __name__ == "__main__":

# Define variables
keycloak_url = "http://localhost:8080/realms/master/protocol/openid-connect/token"
username = "admin"
password = "admin123"
client_id = "admin-cli"
destination_path = sys.argv[1]

token = get_keycloak_acces_token(username=username, password=password, client_id= client_id, keycloak_url=keycloak_url)

print("token=", token)

save_access_token(token, destination_path=destination_path)




Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ version: '3.8'

services:
keycloak:
# image: quay.io/keycloak/keycloak:24.0.1
image: bitnami/keycloak:24
build: .
container_name: keycloak
command: start-dev
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin123
Expand All @@ -14,9 +12,12 @@ services:
volumes:
- .:/opt/keycloak-setup
depends_on:
- db
entrypoint: ["/bin/sh", "-c", "/opt/keycloak-setup/init.sh && /opt/keycloak/bin/kc.sh start-dev"]

db:
condition: service_healthy
networks:
- bridge_network


db:
image: postgres:15
container_name: keycloak-db
Expand All @@ -28,6 +29,17 @@ services:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
retries: 5
start_period: 10s
networks:
- bridge_network

volumes:
pgdata:

networks:
bridge_network:
driver: bridge
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM bitnami/keycloak:24

USER root

# Install jq (and any other necessary dependencies)
RUN apt-get update && apt-get install -y jq

# Set working directory
WORKDIR /opt/keycloak-setup

# Copy the setup scripts to the container
COPY ./init.sh /opt/keycloak-setup/init.sh

# Set permissions to ensure init.sh is executable
RUN chmod +x /opt/keycloak-setup/init.sh

# Set the entrypoint
ENTRYPOINT ["/bin/sh", "-c", "/opt/bitnami/scripts/keycloak/run.sh & sleep 10 && /opt/keycloak-setup/init.sh && tail -f /dev/null"]
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/bin/bash

# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -12,15 +14,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

#!/bin/bash

# Wait for Keycloak to be ready
echo "Waiting for Keycloak to be ready..."
until $(curl --output /dev/null --silent --head --fail http://keycloak:8080/realms/master); do
until curl -sf http://keycloak:8080/realms/master > /dev/null; do
printf '.'
sleep 5
done


echo "Keycloak is ready!"

# Get Admin Token
Expand Down
Loading

0 comments on commit 3804309

Please sign in to comment.