diff --git a/examples/command/portals/ai/azure_openai/.gitignore b/examples/command/portals/ai/azure_openai/.gitignore new file mode 100644 index 00000000000..6373434b0e5 --- /dev/null +++ b/examples/command/portals/ai/azure_openai/.gitignore @@ -0,0 +1,3 @@ +run_ockam_processed.sh +.env.azure +env diff --git a/examples/command/portals/ai/azure_openai/README.md b/examples/command/portals/ai/azure_openai/README.md new file mode 100644 index 00000000000..b2974637cb9 --- /dev/null +++ b/examples/command/portals/ai/azure_openai/README.md @@ -0,0 +1,9 @@ +# Azure OpenAI + +This hands-on example uses Ockam to create an end-to-end encrypted portal to a private Azure OpenAI API. + +We connect a python app in one virtual private network with an Azure OpenAI API configured via private endpoint in another virtual private network. +The example uses the Azure CLI to create these virtual networks. + +You can read a detailed walkthrough of this example at: +https://docs.ockam.io/portals/ai/azure_openai diff --git a/examples/command/portals/ai/azure_openai/ai_corp/run.sh b/examples/command/portals/ai/azure_openai/ai_corp/run.sh new file mode 100755 index 00000000000..6d311925c74 --- /dev/null +++ b/examples/command/portals/ai/azure_openai/ai_corp/run.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +set -ex +run() { + enrollment_ticket="$1" + + # Create Resource Group + echo "Creating Azure Resource Group..." + az group create --name $RESOURCE_GROUP --location $LOCATION + + # Create Virtual Network + echo "Creating Azure Virtual Network..." + az network vnet create \ + --resource-group $RESOURCE_GROUP \ + --name $VNET_NAME \ + --address-prefix "10.0.0.0/16" \ + --subnet-name $SUBNET_NAME \ + --subnet-prefix "10.0.0.0/24" \ + --location $LOCATION + + # Create OpenAI Service + echo "Creating Azure OpenAI Service..." + az cognitiveservices account create \ + --name $SERVICE_NAME \ + --resource-group $RESOURCE_GROUP \ + --kind OpenAI \ + --sku S0 \ + --location $LOCATION \ + --custom-domain $SERVICE_NAME \ + --tags "CreatedBy=AzureCLI" + + # Retrieve virtual network and subnet ID + echo "Retrieving virtual network and subnet ID..." + VNET_ID=$(az network vnet show --name $VNET_NAME --resource-group $RESOURCE_GROUP --query "id" -o tsv) + SUBNET_ID=$(az network vnet subnet show --name $SUBNET_NAME --vnet-name $VNET_NAME --resource-group $RESOURCE_GROUP --query "id" -o tsv) + + # Create private endpoint for the OpenAI service + echo "Creating private endpoint for the OpenAI service..." + az network private-endpoint create \ + --name $PRIVATE_ENDPOINT_NAME \ + --resource-group $RESOURCE_GROUP \ + --vnet-name $VNET_NAME \ + --subnet $SUBNET_NAME \ + --private-connection-resource-id $(az cognitiveservices account show --name $SERVICE_NAME --resource-group $RESOURCE_GROUP --query "id" -o tsv) \ + --group-id "account" \ + --connection-name "${SERVICE_NAME}-connection" + + # Create private DNS zone and linking it... + echo "Creating private DNS zone and linking it..." + az network private-dns zone create \ + --resource-group $RESOURCE_GROUP \ + --name $PRIVATE_DNS_ZONE + + # Link private DNS zone to virtual network... + echo "Linking private DNS zone to virtual network..." + az network private-dns link vnet create \ + --resource-group $RESOURCE_GROUP \ + --zone-name $PRIVATE_DNS_ZONE \ + --name "${VNET_NAME}-link" \ + --virtual-network $VNET_ID \ + --registration-enabled false + + # Configure DNS records... + echo "Configuring DNS records..." + PRIVATE_IP=$(az network private-endpoint show --name $PRIVATE_ENDPOINT_NAME --resource-group $RESOURCE_GROUP --query "customDnsConfigs[0].ipAddresses[0]" -o tsv) + + # Create DNS record set... + echo "Creating DNS record set..." + az network private-dns record-set a create \ + --name $SERVICE_NAME \ + --zone-name $PRIVATE_DNS_ZONE \ + --resource-group $RESOURCE_GROUP + + # Add DNS record... + echo "Adding DNS record..." + az network private-dns record-set a add-record \ + --record-set-name $SERVICE_NAME \ + --zone-name $PRIVATE_DNS_ZONE \ + --resource-group $RESOURCE_GROUP \ + --ipv4-address $PRIVATE_IP + + # Update network ACLs for the OpenAI service to deny public access... + echo "Updating network ACLs for the OpenAI service to deny public access..." + RESOURCE_ID=$(az cognitiveservices account show \ + --name $SERVICE_NAME \ + --resource-group $RESOURCE_GROUP \ + --query id -o tsv) + + # Update network ACLs for the OpenAI service to deny public access... + az resource update \ + --ids $RESOURCE_ID \ + --set properties.networkAcls="{'defaultAction':'Deny'}" \ + --set properties.publicNetworkAccess="Disabled" + + # Deploy model on OpenAI service + echo "Deploying model ${MODEL_NAME} on ${SERVICE_NAME}..." + az cognitiveservices account deployment create \ + --name "$SERVICE_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --deployment-name "$DEPLOYMENT_NAME" \ + --model-name "$MODEL_NAME" \ + --model-version "$MODEL_VERSION" \ + --model-format OpenAI \ + --sku-capacity 1 \ + --sku-name Standard + + # Setting up VM. Processing Ockam setup script... + echo "Setting up VM. Processing Ockam setup script..." + + # Replace variables in the script + sed "s|\${SERVICE_NAME}|${SERVICE_NAME}|g" run_ockam.sh | \ + sed "s|\${TICKET}|${enrollment_ticket}|g" > run_ockam_processed.sh + + chmod +x run_ockam_processed.sh + + # Create VM and run Ockam setup script + az vm create \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vm" \ + --image "RedHat:RHEL:8-lvm-gen2:latest" \ + --admin-username "azureuser" \ + --generate-ssh-keys \ + --vnet-name $VNET_NAME \ + --subnet $SUBNET_NAME \ + --location $LOCATION \ + --custom-data run_ockam_processed.sh \ + --tags "CreatedBy=AzureCLI" + + # Get API key for OpenAI service + API_KEY=$(az cognitiveservices account keys list \ + --name $SERVICE_NAME \ + --resource-group $RESOURCE_GROUP \ + --query "key1" \ + -o tsv) + + # Get Ockam project ID + PROJECT_ID=$(ockam project show --jq .id | tr -d '"') + + # Set Azure OpenAI endpoint + AZURE_OPENAI_ENDPOINT="https://az-openai.${PROJECT_ID}.ockam.network:443" + + echo "AZURE_OPENAI_ENDPOINT=$AZURE_OPENAI_ENDPOINT" > .env.azure + echo "AZURE_OPENAI_API_KEY=$API_KEY" >> .env.azure +} + +cleanup() { + echo "Cleaning up..." + + echo "Deleting VM and associated resources..." + az vm delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vm" \ + --yes \ + --force "yes" + + echo "Deleting disk..." + DISK_NAME=$(az disk list \ + --resource-group $RESOURCE_GROUP \ + --query "[?contains(name, '${SERVICE_NAME}-vm')].name" \ + -o tsv) + + if [ -n "$DISK_NAME" ]; then + echo "Found disk: $DISK_NAME" + az disk delete \ + -g "$RESOURCE_GROUP" \ + -n "$DISK_NAME" \ + --yes + fi + + echo "Deleting NIC..." + az network nic delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vmVMNic" + + echo "Deleting public IP..." + az network public-ip delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vmPublicIP" + + echo "Deleting NSG..." + az network nsg delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vmNSG" + + echo "Deleting Private Endpoint..." + az network private-endpoint delete \ + --name $PRIVATE_ENDPOINT_NAME \ + --resource-group $RESOURCE_GROUP + + echo "Deleting Azure OpenAI Service..." + az cognitiveservices account delete \ + --name $SERVICE_NAME \ + --resource-group $RESOURCE_GROUP + + echo "Purging Azure OpenAI Service..." + az cognitiveservices account purge \ + --name $SERVICE_NAME \ + --resource-group $RESOURCE_GROUP \ + --location $LOCATION \ + || echo "Purge not needed or already completed" + + echo "Deleting Virtual Network..." + az network vnet delete \ + --name $VNET_NAME \ + --resource-group $RESOURCE_GROUP + + echo "Deleting Resource Group..." + az group delete \ + --name $RESOURCE_GROUP \ + --yes + + echo "Deleting local files" + rm run_ockam_processed.sh + rm .env.azure +} + + +user="" +command -v sha256sum &>/dev/null && user=$(az ad signed-in-user show --query userPrincipalName -o tsv | sha256sum | cut -c 1-20) +command -v shasum &>/dev/null && user=$(az ad signed-in-user show --query userPrincipalName -o tsv | shasum -a 256 | cut -c 1-20) +export name="ockam-ex-az-openai-aicorp-$user" + +export RESOURCE_GROUP="${name}-rg" +export LOCATION="eastus" + +# VNet +export VNET_NAME="${name}-vnet" +export SUBNET_NAME="${name}-subnet" + +# OpenAI Service +export SERVICE_NAME="${name}-openai" +export PRIVATE_DNS_ZONE="privatelink.openai.azure.com" +export PRIVATE_ENDPOINT_NAME="${SERVICE_NAME}-private-endpoint" + +# Model +export DEPLOYMENT_NAME="gpt-4o-mini" +export MODEL_NAME="gpt-4o-mini" +export MODEL_VERSION="2024-07-18" + + +# Check if the first argument is "cleanup" +# If it is, call the cleanup function. If not, call the run function. +if [ "$1" = "cleanup" ]; then cleanup; else run "$1"; fi diff --git a/examples/command/portals/ai/azure_openai/ai_corp/run_ockam.sh b/examples/command/portals/ai/azure_openai/ai_corp/run_ockam.sh new file mode 100755 index 00000000000..cced59cb001 --- /dev/null +++ b/examples/command/portals/ai/azure_openai/ai_corp/run_ockam.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -ex + +echo "Starting Ockam installation..." +if ! curl --proto '=https' --tlsv1.2 -sSfL https://install.command.ockam.io | bash; then + echo "ERROR: Failed to install Ockam Command" + exit 1 +fi +source "$HOME/.ockam/env" + +# Verify Ockam installation +if ! ockam --version; then + echo "ERROR: Ockam installation verification failed" + exit 1 +else + echo "Ockam installation verification successful" +fi + +echo "Setup ockam node" +export OCKAM_LOG_MAX_FILES=5 +export OCKAM_HOME=/opt/ockam_home +export OCKAM_OPENTELEMETRY_EXPORT=false + +cat << OCKAMCONFIG > /opt/ockam.json +{ + "relay": "openai-relay", + "tcp-outlet": { + "to": "${SERVICE_NAME}.openai.azure.com:443", + "allow": "azure-openai-inlet", + "tls": true + } +} +OCKAMCONFIG + +echo "Config file: $(cat /opt/ockam.json)" + +MARKER_FILE="/opt/ockam_setup_complete.marker" + +ENROLLMENT_TICKET=${TICKET} + +echo "Check if the $MARKER_FILE file exists to decide to restore or setup a new node" +if [ -f "$MARKER_FILE" ]; then + echo "Marker file $MARKER_FILE exists, Restoring state" + ockam node delete --all --yes + ockam node create /opt/ockam.json +else + echo "Enrolling project with ticket" + ockam node create /opt/ockam.json --enrollment-ticket "$ENROLLMENT_TICKET" + touch "$MARKER_FILE" +fi + +echo "Startup script completed successfully" diff --git a/examples/command/portals/ai/azure_openai/health_corp/client.py b/examples/command/portals/ai/azure_openai/health_corp/client.py new file mode 100644 index 00000000000..edee739578b --- /dev/null +++ b/examples/command/portals/ai/azure_openai/health_corp/client.py @@ -0,0 +1,25 @@ +import os +from openai import AzureOpenAI + +# Initialize Azure OpenAI client +client = AzureOpenAI( + azure_endpoint=os.getenv('AZURE_OPENAI_ENDPOINT'), + api_key=os.getenv('AZURE_OPENAI_API_KEY'), + api_version="2024-02-15-preview" +) + +try: + print(f"Connecting to: {client.base_url}") + + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "user", "content": "What is Ockham's Razor?"} + ] + ) + + print("\nResponse:", response.choices[0].message.content) + print("\nThe example run was successful 🥳.") + +except Exception as e: + print(f"An error occurred: {e}") diff --git a/examples/command/portals/ai/azure_openai/health_corp/run.sh b/examples/command/portals/ai/azure_openai/health_corp/run.sh new file mode 100755 index 00000000000..6f37f867a57 --- /dev/null +++ b/examples/command/portals/ai/azure_openai/health_corp/run.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +set -ex +run() { + enrollment_ticket="$1" + + # Create Resource Group + echo "Creating Azure Resource Group..." + az group create --name $RESOURCE_GROUP --location $LOCATION + + # Create Virtual Network + echo "Creating Azure Virtual Network..." + az network vnet create \ + --resource-group $RESOURCE_GROUP \ + --name $VNET_NAME \ + --address-prefix "192.168.0.0/16" \ + --subnet-name $SUBNET_NAME \ + --subnet-prefix "192.168.0.0/24" \ + --location $LOCATION + + # Process Ockam setup script... + echo "Processing Ockam setup script..." + + sed "s|\${SERVICE_NAME}|${SERVICE_NAME}|g" run_ockam.sh | \ + sed "s|\${TICKET}|${enrollment_ticket}|g" > run_ockam_processed.sh + + chmod +x run_ockam_processed.sh + + # Create VM and run Ockam setup script + echo "Creating VM..." + az vm create \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vm" \ + --image "RedHat:RHEL:8-lvm-gen2:latest" \ + --admin-username "azureuser" \ + --generate-ssh-keys \ + --vnet-name $VNET_NAME \ + --subnet $SUBNET_NAME \ + --location $LOCATION \ + --custom-data run_ockam_processed.sh \ + --tags "CreatedBy=AzureCLI" + + PUBLIC_IP=$(az vm show \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vm" \ + --show-details \ + --query "publicIps" \ + -o tsv) + # Copy client.py and .env.azure to VM + until scp -o StrictHostKeyChecking=no ./client.py "azureuser@$PUBLIC_IP:~/client.py"; do sleep 10; done + until scp -o StrictHostKeyChecking=no ../ai_corp/.env.azure "azureuser@$PUBLIC_IP:~/.env.azure"; do sleep 10; done + + # Run client.py script on VM + ssh -o StrictHostKeyChecking=no "azureuser@$PUBLIC_IP" \ + 'bash -s' << 'EOS' + sudo dnf install -y python39 python39-pip + python3.9 -m pip install --user openai + export $(cat .env.azure | xargs) && python3.9 /home/azureuser/client.py +EOS + +} + +cleanup() { + echo "Cleaning up..." + + echo "Deleting VM and associated resources..." + az vm delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vm" \ + --yes \ + --force "yes" + + echo "Deleting disk..." + DISK_NAME=$(az disk list \ + --resource-group $RESOURCE_GROUP \ + --query "[?contains(name, '${SERVICE_NAME}-vm')].name" \ + -o tsv) + + if [ -n "$DISK_NAME" ]; then + echo "Found disk: $DISK_NAME" + az disk delete \ + -g "$RESOURCE_GROUP" \ + -n "$DISK_NAME" \ + --yes + fi + + echo "Deleting NIC..." + az network nic delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vmVMNic" + + echo "Deleting public IP..." + az network public-ip delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vmPublicIP" + + echo "Deleting NSG..." + az network nsg delete \ + --resource-group $RESOURCE_GROUP \ + --name "${SERVICE_NAME}-vmNSG" + + + echo "Deleting Virtual Network..." + az network vnet delete \ + --name $VNET_NAME \ + --resource-group $RESOURCE_GROUP + + echo "Deleting Resource Group..." + az group delete \ + --name $RESOURCE_GROUP \ + --yes + + echo "Deleting local files" + rm run_ockam_processed.sh +} + + +user="" +command -v sha256sum &>/dev/null && user=$(az ad signed-in-user show --query userPrincipalName -o tsv | sha256sum | cut -c 1-20) +command -v shasum &>/dev/null && user=$(az ad signed-in-user show --query userPrincipalName -o tsv | shasum -a 256 | cut -c 1-20) +export name="ockam-ex-openai-healthcorp-$user" + +export RESOURCE_GROUP="${name}-rg" +export LOCATION="eastus" + +# VNet +export VNET_NAME="${name}-vnet" +export SUBNET_NAME="${name}-subnet" + +# Service +export SERVICE_NAME="${name}-openai" + +# Check if the first argument is "cleanup" +# If it is, call the cleanup function. If not, call the run function. +if [ "$1" = "cleanup" ]; then cleanup; else run "$1"; fi diff --git a/examples/command/portals/ai/azure_openai/health_corp/run_ockam.sh b/examples/command/portals/ai/azure_openai/health_corp/run_ockam.sh new file mode 100755 index 00000000000..0cf0edf55bd --- /dev/null +++ b/examples/command/portals/ai/azure_openai/health_corp/run_ockam.sh @@ -0,0 +1,52 @@ +#!/bin/bash +set -ex + +echo "Starting Ockam installation..." +if ! curl --proto '=https' --tlsv1.2 -sSfL https://install.command.ockam.io | bash; then + echo "ERROR: Failed to install Ockam Command" + exit 1 +fi +source "$HOME/.ockam/env" + +# Verify Ockam installation +if ! ockam --version; then + echo "ERROR: Ockam installation verification failed" + exit 1 +else + echo "Ockam installation verification successful" +fi + +echo "Setup ockam node" +export OCKAM_LOG_MAX_FILES=5 +export OCKAM_HOME=/opt/ockam_home +export OCKAM_OPENTELEMETRY_EXPORT=false + +cat << OCKAMCONFIG > /opt/ockam.json +{ + "tcp-inlet": { + "from": "0.0.0.0:443", + "via": "openai-relay", + "allow": "azure-openai-outlet", + "tls": true + } +} +OCKAMCONFIG + +echo "Config file: $(cat /opt/ockam.json)" + +MARKER_FILE="/opt/ockam_setup_complete.marker" + +ENROLLMENT_TICKET=${TICKET} + +echo "Check if the $MARKER_FILE file exists to decide to restore or setup a new node" +if [ -f "$MARKER_FILE" ]; then + echo "Marker file $MARKER_FILE exists, Restoring state" + ockam node delete --all --yes + ockam node create /opt/ockam.json +else + echo "Enrolling project with ticket" + ockam node create /opt/ockam.json --enrollment-ticket "$ENROLLMENT_TICKET" + touch "$MARKER_FILE" +fi + +echo "Startup script completed successfully" diff --git a/examples/command/portals/ai/azure_openai/run.sh b/examples/command/portals/ai/azure_openai/run.sh new file mode 100755 index 00000000000..41715a4faa5 --- /dev/null +++ b/examples/command/portals/ai/azure_openai/run.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -e + +# This script, `./run.sh ...` is invoked on a developer’s work machine. +# +# This hands-on example uses Ockam to create an end-to-end encrypted portal to an Azure OpenAI Endpoint. We connect a Python +# client in one Azure VNet with a Azure OpenAI Endpoint in another Azure VNet. +# +# The example uses Azure CLI to create these VPCs. +# +# You can read a detailed walkthrough of this example at: +# https://docs.ockam.io/portals/ai/azure_openai + +run() { + # Run `ockam enroll`. + # + # The enroll command creates a new vault and generates a cryptographic identity with private keys stored in that + # vault. It then guides you to sign in to Ockam Orchestrator. + # + # If this is your first time signing in, the Orchestrator creates a new dedicated project for you. A project offers + # two services: a membership authority and a relay service. + # + # The enroll command then asks this project’s membership authority to sign and issue a credential that attests that + # your identifier is a member of this project. Since your account in Orchestrator is the creator and hence first + # administrator on this new project, the membership authority issues this credential. The enroll command stores the + # credential for later use and exits. + ockam enroll + + # Create an enrollment ticket to enroll the identity used by an ockam node that will run adjacent to the api + # server in ai_corp's network. + # + # The identity that enrolls with the generated ticket will be given a project membership credential in which the + # project membership authority will cryptographically attest to specified attributes - monitoring-api-outlet=true. + # + # The identity will also allowed to create a relay in the project at the address `monitoring-api`. + ai_corp_ticket=$(ockam project ticket --usage-count 1 --expires-in 60m \ + --attribute azure-openai-outlet --relay openai-relay) + + # Create an enrollment ticket to enroll the identity used by an ockam node that will run adjacent to the postgres + # client app in health_corp_ticket's network. + # + # The identity that enrolls with the generated ticket will be given a project membership credential in which the + # project membership authority will cryptographically attest to specified attributes - monitoring-api-inlet=true. + health_corp_ticket=$(ockam project ticket --usage-count 1 --expires-in 60m \ + --attribute azure-openai-inlet --tls) + + if [[ -n "$OCKAM_VERSION" ]]; then + export OCKAM_VERSION="v${OCKAM_VERSION}"; + fi + + # Invoke `ai_corp/run.sh` in the directory that has ai_corp's configuration. Pass the above + # enrollment ticket as the first argument to run.sh script. Read ai_corp/run.sh to understand the parts + # that are provisioned in ai_corp's virtual private cloud. + echo; pushd ai_corp; ./run.sh "$ai_corp_ticket"; popd + + # Invoke `health_corp/run.sh` in the directory that has health_corp's configuration. Pass the above + # enrollment ticket as the first argument to run.sh script. Read health_corp/run.sh to understand the parts + # that are provisioned in health_corp's virtual private cloud. + echo; pushd health_corp; ./run.sh "$health_corp_ticket"; popd +} + +# Cleanup after the example - `./run.sh cleanup` +# Remove all resources that were created in Azure. +cleanup() { + pushd ai_corp; ./run.sh cleanup; popd + pushd health_corp; ./run.sh cleanup; popd +} + +# Check if Ockam Command is already installed and available in path. +# If it's not, then install it (only if we are not cleaning up) +if ! type ockam &>/dev/null && ! [[ "$1" = "cleanup" ]]; then + curl --proto '=https' --tlsv1.2 -sSfL https://install.command.ockam.io | bash + source "$HOME/.ockam/env" +fi + +# Check that tools we need are installed. +for c in az curl; do + if ! type "$c" &>/dev/null; then echo "ERROR: Please install: $c" && exit 1; fi +done + +# Check if the first argument is "cleanup" +# If it is, call the cleanup function. If not, call the run function. +if [ "$1" = "cleanup" ]; then cleanup; else run; fi