zitinexus-router-script/Router-enrollment-script/enroll-router.sh

712 lines
21 KiB
Bash

#!/bin/bash
# OpenZiti Router Enrollment Script
# Compatible with Ubuntu 22.04, 24.04 and other Linux distributions
# This script automates the router enrollment process using hash key
set -euo pipefail
# Script configuration
SCRIPT_VERSION="1.0.0"
SCRIPT_NAME="OpenZiti Router Enrollment Script"
LOG_FILE="/var/log/ziti-router-enrollment.log"
CONFIG_DIR="/etc/zitirouter"
CERTS_DIR="${CONFIG_DIR}/certs"
ROUTER_CONFIG="${CONFIG_DIR}/router.yaml"
JWT_FILE="${CONFIG_DIR}/enrollment.jwt"
SYSTEMD_SERVICE_FILE="/etc/systemd/system/ziti-router.service"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default API endpoint (can be overridden)
DEFAULT_API_ENDPOINT="https://backend.zitinexus.com"
# Initialize variables to prevent unbound variable errors
CALLBACK_URL=""
JWT=""
ROUTER_YAML=""
ROUTER_NAME=""
ROUTER_ID=""
TENANT_ID=""
CONTROLLER_ENDPOINT=""
ROLE_ATTRIBUTES=""
HASH_KEY=""
API_ENDPOINT=""
# Logging function
log() {
local level=$1
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" >/dev/null 2>&1 || true
case $level in
"ERROR")
echo -e "${RED}[ERROR]${NC} $message" >&2
;;
"SUCCESS")
echo -e "${GREEN}[SUCCESS]${NC} $message"
;;
"WARNING")
echo -e "${YELLOW}[WARNING]${NC} $message"
;;
"INFO")
echo -e "${BLUE}[INFO]${NC} $message"
;;
*)
echo "$message"
;;
esac
}
# Error handling
error_exit() {
log "ERROR" "$1"
exit 1
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
error_exit "This script must be run as root (use sudo)"
fi
}
# Check system requirements
check_requirements() {
log "INFO" "Checking system requirements..."
# Check if curl is installed
if ! command -v curl &> /dev/null; then
log "INFO" "Installing curl..."
apt-get update && apt-get install -y curl || error_exit "Failed to install curl"
fi
# Check if jq is installed
if ! command -v jq &> /dev/null; then
log "INFO" "Installing jq..."
apt-get update && apt-get install -y jq || error_exit "Failed to install jq"
fi
# Check if systemctl is available
if ! command -v systemctl &> /dev/null; then
error_exit "systemctl is required but not available"
fi
log "SUCCESS" "System requirements check passed"
}
# Install OpenZiti if not present
install_ziti() {
if command -v ziti &> /dev/null; then
local ziti_version=$(ziti version 2>/dev/null | head -n1 || echo "unknown")
log "INFO" "OpenZiti CLI already installed: $ziti_version"
return 0
fi
log "INFO" "Installing OpenZiti CLI using package repository..."
# Method 1: Use OpenZiti package repository (your preferred method)
log "INFO" "Setting up OpenZiti package repository..."
# Step 1: Add GPG key
if curl -sSLf https://get.openziti.io/tun/package-repos.gpg | gpg --dearmor --output /usr/share/keyrings/openziti.gpg; then
log "INFO" "GPG key added successfully"
else
error_exit "Failed to add OpenZiti GPG key"
fi
# Step 2: Set proper permissions
chmod a+r /usr/share/keyrings/openziti.gpg || error_exit "Failed to set GPG key permissions"
# Step 3: Add repository to sources list
tee /etc/apt/sources.list.d/openziti-release.list >/dev/null <<EOF
deb [signed-by=/usr/share/keyrings/openziti.gpg] https://packages.openziti.org/zitipax-openziti-deb-stable debian main
EOF
if [[ $? -eq 0 ]]; then
log "INFO" "Repository added successfully"
else
error_exit "Failed to add OpenZiti repository"
fi
# Step 4: Update package list
log "INFO" "Updating package list..."
if apt-get update; then
log "INFO" "Package list updated successfully"
else
error_exit "Failed to update package list"
fi
# Step 5: Install openziti-router package
log "INFO" "Installing openziti-router package..."
if apt-get install -y openziti-router; then
log "SUCCESS" "OpenZiti router package installed successfully"
else
log "WARNING" "Failed to install openziti-router package, trying fallback method..."
# Fallback: Try to install just the ziti CLI
log "INFO" "Attempting to install ziti CLI only..."
if apt-get install -y ziti; then
log "SUCCESS" "OpenZiti CLI installed successfully"
else
error_exit "Failed to install OpenZiti CLI from package repository"
fi
fi
# Verify installation
if command -v ziti &> /dev/null; then
local ziti_version=$(ziti version 2>/dev/null | head -n1 || echo "unknown")
log "SUCCESS" "OpenZiti CLI installed successfully: $ziti_version"
else
error_exit "OpenZiti CLI installation failed - command not found after installation"
fi
}
# Create necessary directories
create_directories() {
log "INFO" "Creating necessary directories..."
mkdir -p "$CONFIG_DIR" || error_exit "Failed to create config directory"
mkdir -p "$CERTS_DIR" || error_exit "Failed to create certs directory"
mkdir -p "$(dirname "$LOG_FILE")" || error_exit "Failed to create log directory"
# Set proper permissions
chmod 755 "$CONFIG_DIR"
chmod 700 "$CERTS_DIR"
log "SUCCESS" "Directories created successfully"
}
# Get user input
get_user_input() {
echo
echo "=============================================="
echo " $SCRIPT_NAME v$SCRIPT_VERSION"
echo "=============================================="
echo
# Get API endpoint
read -p "Enter ZitiNexus Portal API endpoint [$DEFAULT_API_ENDPOINT]: " api_endpoint
API_ENDPOINT="${api_endpoint:-$DEFAULT_API_ENDPOINT}"
# Validate API endpoint format
if [[ ! "$API_ENDPOINT" =~ ^https?:// ]]; then
error_exit "API endpoint must start with http:// or https://"
fi
# Get hash key
echo
echo "Enter the router enrollment hash key from ZitiNexus Portal:"
echo "(This is a 32-character hash key provided when you created the router enrollment)"
read -p "Hash Key: " hash_key
# Validate hash key format
if [[ ! "$hash_key" =~ ^[a-fA-F0-9]{32}$ ]]; then
error_exit "Invalid hash key format. Expected 32-character hexadecimal string."
fi
HASH_KEY="$hash_key"
echo
log "INFO" "Configuration:"
log "INFO" " API Endpoint: $API_ENDPOINT"
log "INFO" " Hash Key: ${HASH_KEY:0:8}...${HASH_KEY:24:8}"
echo
}
# Register router with API
register_router() {
log "INFO" "Registering router with ZitiNexus Portal..."
local api_url="${API_ENDPOINT}/api/router/register"
local payload="{\"hashKey\":\"$HASH_KEY\"}"
local response_file=$(mktemp)
local http_code
# Debug: Show the URL being called
log "INFO" "API URL: $api_url"
# Make API call with retry logic
local max_retries=3
local retry_count=0
while [ $retry_count -lt $max_retries ]; do
http_code=$(curl -s -w "%{http_code}" -o "$response_file" \
-X POST \
-H "Content-Type: application/json" \
-H "User-Agent: ZitiRouter-EnrollmentScript/$SCRIPT_VERSION" \
-d "$payload" \
--connect-timeout 30 \
--max-time 60 \
"$api_url" 2>/dev/null || echo "000")
if [[ "$http_code" == "200" ]]; then
break
elif [[ "$http_code" == "429" ]]; then
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
local wait_time=$((retry_count * 2))
log "WARNING" "Rate limited. Waiting ${wait_time}s before retry $retry_count/$max_retries..."
sleep $wait_time
fi
else
break
fi
done
# Check response
if [[ "$http_code" != "200" ]]; then
local error_msg="API request failed with HTTP $http_code"
if [[ -f "$response_file" ]]; then
local api_error=$(jq -r '.error.message // .message // "Unknown error"' "$response_file" 2>/dev/null || echo "Unknown error")
error_msg="$error_msg: $api_error"
fi
rm -f "$response_file"
error_exit "$error_msg"
fi
# Parse response
if ! jq -e '.success' "$response_file" >/dev/null 2>&1; then
local api_error=$(jq -r '.error.message // .message // "Registration failed"' "$response_file" 2>/dev/null || echo "Registration failed")
rm -f "$response_file"
error_exit "Registration failed: $api_error"
fi
# Extract data from response - Updated to match expected JSON structure
JWT=$(jq -r '.data.jwt' "$response_file" 2>/dev/null)
ROUTER_YAML=$(jq -r '.data.routerConfig.yaml' "$response_file" 2>/dev/null)
ROUTER_NAME=$(jq -r '.data.routerInfo.name' "$response_file" 2>/dev/null)
CALLBACK_URL=$(jq -r '.data.callbackUrl // ""' "$response_file" 2>/dev/null)
# Extract additional data for proper router configuration
ROUTER_ID=$(jq -r '.data.routerInfo.id' "$response_file" 2>/dev/null)
TENANT_ID=$(jq -r '.data.metadata.tenantId' "$response_file" 2>/dev/null)
CONTROLLER_ENDPOINT=$(jq -r '.data.metadata.controllerEndpoint' "$response_file" 2>/dev/null)
# Extract role attributes as array
ROLE_ATTRIBUTES=$(jq -r '.data.routerInfo.roleAttributes[]' "$response_file" 2>/dev/null | tr '\n' ' ')
# Store the full response for debugging
FULL_RESPONSE=$(cat "$response_file")
rm -f "$response_file"
# Validate extracted data
if [[ -z "$JWT" || "$JWT" == "null" ]]; then
error_exit "Failed to extract JWT from API response"
fi
if [[ -z "$ROUTER_YAML" || "$ROUTER_YAML" == "null" ]]; then
error_exit "Failed to extract router configuration from API response"
fi
if [[ -z "$ROUTER_NAME" || "$ROUTER_NAME" == "null" ]]; then
error_exit "Failed to extract router name from API response"
fi
if [[ -z "$ROUTER_ID" || "$ROUTER_ID" == "null" ]]; then
error_exit "Failed to extract router ID from API response"
fi
if [[ -z "$TENANT_ID" || "$TENANT_ID" == "null" ]]; then
error_exit "Failed to extract tenant ID from API response"
fi
# Initialize CALLBACK_URL as empty string if null
if [[ "$CALLBACK_URL" == "null" ]]; then
CALLBACK_URL=""
fi
log "SUCCESS" "Router registered successfully: $ROUTER_NAME (ID: $ROUTER_ID)"
log "INFO" " Tenant ID: $TENANT_ID"
log "INFO" " Controller: $CONTROLLER_ENDPOINT"
log "INFO" " Role Attributes: $ROLE_ATTRIBUTES"
}
# Fix router configuration for proper enrollment
fix_router_configuration() {
log "INFO" "Constructing router configuration from API response..."
# Create a backup of the original config
cp "$ROUTER_CONFIG" "${ROUTER_CONFIG}.backup" || error_exit "Failed to backup router configuration"
# Use data from API response instead of hardcoding
log "INFO" "Using API response data to construct proper router configuration"
# Determine controller endpoint - use from API if available, otherwise default
local ctrl_endpoint="enroll.zitinexus.com:443"
if [[ -n "$CONTROLLER_ENDPOINT" && "$CONTROLLER_ENDPOINT" != "null" ]]; then
ctrl_endpoint="$CONTROLLER_ENDPOINT"
fi
# Add tls: prefix if not present
if [[ ! "$ctrl_endpoint" =~ ^tls: ]]; then
ctrl_endpoint="tls:$ctrl_endpoint"
fi
# Build role attributes section from API response
local role_attributes_section=""
if [[ -n "$ROLE_ATTRIBUTES" && "$ROLE_ATTRIBUTES" != " " ]]; then
role_attributes_section="roleAttributes:"
for attr in $ROLE_ATTRIBUTES; do
role_attributes_section="$role_attributes_section"$'\n'" - \"$attr\""
done
else
role_attributes_section="# No role attributes specified"
fi
# Get current timestamp if not provided
local generated_at=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
# Generate the complete router configuration using API data
cat > "$ROUTER_CONFIG" << EOF
v: 3
identity:
cert: /etc/zitirouter/certs/$ROUTER_NAME.cert
server_cert: /etc/zitirouter/certs/$ROUTER_NAME.server.chain.cert
key: /etc/zitirouter/certs/$ROUTER_NAME.key
ca: /etc/zitirouter/certs/$ROUTER_NAME.cas
ctrl:
endpoint: $ctrl_endpoint
link:
dialers:
- binding: transport
listeners:
# bindings of edge and tunnel requires an "edge" section below
- binding: edge
address: tls:0.0.0.0:443
options:
advertise: 127.0.0.1:443
connectTimeoutMs: 5000
getSessionTimeout: 60
- binding: tunnel
options:
mode: host
edge:
csr:
country: SG
province: SG
locality: Singapore
organization: Genworx
organizationalUnit: ZitiNexus
sans:
dns:
- localhost
- $ROUTER_NAME
ip:
- "127.0.0.1"
- "::1"
# Tenant-specific role attributes
$role_attributes_section
# Router metadata
metadata:
tenantId: "$TENANT_ID"
zitiRouterId: "$ROUTER_ID"
routerType: "private-edge"
generatedAt: "$generated_at"
generatedBy: "ZitiNexus"
EOF
chmod 644 "$ROUTER_CONFIG"
log "SUCCESS" "Router configuration constructed from API response"
log "INFO" " Original config backed up to: ${ROUTER_CONFIG}.backup"
log "INFO" " Router name: $ROUTER_NAME"
log "INFO" " Tenant ID: $TENANT_ID"
log "INFO" " Ziti Router ID: $ROUTER_ID"
log "INFO" " Controller endpoint: $ctrl_endpoint"
log "INFO" " Role attributes: $ROLE_ATTRIBUTES"
}
# Save configuration files
save_configuration() {
log "INFO" "Saving configuration files..."
# Save JWT
echo "$JWT" > "$JWT_FILE" || error_exit "Failed to save JWT file"
chmod 600 "$JWT_FILE"
# Save router configuration
echo "$ROUTER_YAML" > "$ROUTER_CONFIG" || error_exit "Failed to save router configuration"
chmod 644 "$ROUTER_CONFIG"
log "SUCCESS" "Configuration files saved"
log "INFO" " JWT: $JWT_FILE"
log "INFO" " Config: $ROUTER_CONFIG"
# Fix the router configuration for proper enrollment
fix_router_configuration
}
# Enroll router with OpenZiti
enroll_router() {
log "INFO" "Enrolling router with OpenZiti controller..."
# Run router enrollment (correct command syntax)
if ziti router enroll --jwt "$JWT_FILE" "$ROUTER_CONFIG" 2>&1 | tee -a "$LOG_FILE"; then
log "SUCCESS" "Router enrollment completed successfully"
else
error_exit "Router enrollment failed"
fi
# Verify certificates were created
if [[ ! -f "${CERTS_DIR}/${ROUTER_NAME}.cert" ]]; then
error_exit "Router certificate not found after enrollment"
fi
log "SUCCESS" "Router certificates generated successfully"
}
# Create systemd service
create_systemd_service() {
log "INFO" "Creating systemd service..."
# Create the final router config file path for systemd
local final_config="/etc/zitirouter/zitirouter.yaml"
# Copy the router config to the expected location for systemd
cp "$ROUTER_CONFIG" "$final_config" || error_exit "Failed to copy router config to final location"
chmod 644 "$final_config"
cat > "$SYSTEMD_SERVICE_FILE" << EOF
[Unit]
Description=OpenZiti Router
After=network.target
[Service]
ExecStart=/usr/bin/ziti router run /etc/zitirouter/zitirouter.yaml
Restart=on-failure
User=root
WorkingDirectory=/etc/zitirouter
LimitNOFILE=65536
StandardOutput=append:/var/log/ziti-router.log
StandardError=append:/var/log/ziti-router.log
[Install]
WantedBy=multi-user.target
EOF
# Reload systemd and enable service
systemctl daemon-reload || error_exit "Failed to reload systemd"
systemctl enable ziti-router.service || error_exit "Failed to enable ziti-router service"
log "SUCCESS" "Systemd service created and enabled"
log "INFO" " Service file: $SYSTEMD_SERVICE_FILE"
log "INFO" " Config file: $final_config"
log "INFO" " Log file: /var/log/ziti-router.log"
}
# Start router service
start_router() {
log "INFO" "Starting OpenZiti router service..."
if systemctl start ziti-router.service; then
log "SUCCESS" "Router service started successfully"
# Wait a moment and check status
sleep 3
if systemctl is-active --quiet ziti-router.service; then
log "SUCCESS" "Router service is running"
else
log "WARNING" "Router service may not be running properly"
log "INFO" "Check status with: systemctl status ziti-router.service"
fi
else
error_exit "Failed to start router service"
fi
}
# Report enrollment status back to portal
report_status() {
log "INFO" "Reporting enrollment status to portal..."
if [[ -z "$CALLBACK_URL" || "$CALLBACK_URL" == "null" ]]; then
log "WARNING" "No callback URL provided, skipping status report"
return 0
fi
# Fix callback URL domain mismatch if needed
# Replace api.zitinexus.com with backend.zitinexus.com to match script's API endpoint
local fixed_callback_url="$CALLBACK_URL"
if [[ "$CALLBACK_URL" == *"api.zitinexus.com"* ]]; then
fixed_callback_url="${CALLBACK_URL//api.zitinexus.com/backend.zitinexus.com}"
log "INFO" "Fixed callback URL domain: $fixed_callback_url"
fi
# Get system information
local hostname=$(hostname)
local arch=$(uname -m)
local os=$(lsb_release -d 2>/dev/null | cut -f2 || echo "Linux")
local ziti_version=$(ziti version 2>/dev/null | head -n1 || echo "unknown")
local payload=$(cat << EOF
{
"hashKey": "$HASH_KEY",
"status": "success",
"routerInfo": {
"version": "$ziti_version",
"hostname": "$hostname",
"arch": "$arch",
"os": "$os"
}
}
EOF
)
local response_file=$(mktemp)
local http_code
http_code=$(curl -s -w "%{http_code}" -o "$response_file" \
-X POST \
-H "Content-Type: application/json" \
-H "User-Agent: ZitiRouter-EnrollmentScript/$SCRIPT_VERSION" \
-d "$payload" \
--connect-timeout 30 \
--max-time 60 \
"$fixed_callback_url" 2>/dev/null || echo "000")
if [[ "$http_code" == "200" ]]; then
log "SUCCESS" "Enrollment status reported successfully"
else
log "WARNING" "Failed to report enrollment status (HTTP $http_code)"
if [[ -f "$response_file" ]]; then
local error_msg=$(jq -r '.error.message // .message // "Unknown error"' "$response_file" 2>/dev/null || echo "Unknown error")
log "WARNING" "Error: $error_msg"
fi
fi
rm -f "$response_file"
}
# Report failure status
report_failure() {
local error_message="$1"
if [[ -z "$CALLBACK_URL" || "$CALLBACK_URL" == "null" || -z "$HASH_KEY" ]]; then
return 0
fi
# Fix callback URL domain mismatch if needed
local fixed_callback_url="$CALLBACK_URL"
if [[ "$CALLBACK_URL" == *"api.zitinexus.com"* ]]; then
fixed_callback_url="${CALLBACK_URL//api.zitinexus.com/backend.zitinexus.com}"
fi
local payload=$(cat << EOF
{
"hashKey": "$HASH_KEY",
"status": "failed",
"error": "$error_message"
}
EOF
)
curl -s -X POST \
-H "Content-Type: application/json" \
-H "User-Agent: ZitiRouter-EnrollmentScript/$SCRIPT_VERSION" \
-d "$payload" \
--connect-timeout 10 \
--max-time 30 \
"$fixed_callback_url" >/dev/null 2>&1 || true
}
# Cleanup function
cleanup() {
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log "ERROR" "Script failed with exit code $exit_code"
if [[ -n "${HASH_KEY:-}" ]]; then
report_failure "Router enrollment script failed"
fi
fi
}
# Show final status
show_final_status() {
echo
echo "=============================================="
echo " ENROLLMENT COMPLETED SUCCESSFULLY"
echo "=============================================="
echo
log "SUCCESS" "OpenZiti Router enrollment completed!"
echo
echo "Router Information:"
echo " Name: $ROUTER_NAME"
echo " Config: $ROUTER_CONFIG"
echo " Certificates: $CERTS_DIR"
echo " Service: ziti-router.service"
echo
echo "Useful Commands:"
echo " Check status: systemctl status ziti-router"
echo " View logs: journalctl -u ziti-router -f"
echo " Stop router: systemctl stop ziti-router"
echo " Start router: systemctl start ziti-router"
echo " Restart router: systemctl restart ziti-router"
echo
echo "Log file: $LOG_FILE"
echo
}
# Main execution
main() {
# Set up error handling
trap cleanup EXIT
log "INFO" "Starting $SCRIPT_NAME v$SCRIPT_VERSION"
# Check if running as root
check_root
# Check system requirements
check_requirements
# Install OpenZiti if needed
install_ziti
# Create directories
create_directories
# Get user input
get_user_input
# Register router with API
register_router
# Save configuration files
save_configuration
# Enroll router
enroll_router
# Create systemd service
create_systemd_service
# Start router
start_router
# Report success status
report_status
# Show final status
show_final_status
log "SUCCESS" "Router enrollment process completed successfully"
}
# Run main function
main "$@"