zitinexus-router-script/UI/includes/enrollment.php

592 lines
20 KiB
PHP

<?php
/**
* Enrollment Manager for Ziti Router
* Replicates the functionality of the bash script
*/
require_once 'config.php';
require_once 'api_client.php';
class EnrollmentManager {
private $apiClient;
private $routerData;
private $progressCallback;
public function __construct($apiEndpoint = DEFAULT_API_ENDPOINT) {
$this->apiClient = new ApiClient($apiEndpoint);
$this->routerData = [];
}
/**
* Set progress callback function
*/
public function setProgressCallback($callback) {
$this->progressCallback = $callback;
}
/**
* Report progress
*/
private function reportProgress($step, $message, $percentage = null) {
logMessage('INFO', "[$step] $message");
if ($this->progressCallback && is_callable($this->progressCallback)) {
call_user_func($this->progressCallback, $step, $message, $percentage);
}
}
/**
* Main enrollment process
*/
public function enrollRouter($hashKey, $apiEndpoint = null) {
try {
if ($apiEndpoint) {
$this->apiClient = new ApiClient($apiEndpoint);
}
$this->reportProgress('INIT', 'Starting router enrollment process...', 0);
// Step 1: Check system requirements
$this->reportProgress('REQUIREMENTS', 'Checking system requirements...', 10);
if (!$this->checkSystemRequirements()) {
throw new Exception('System requirements check failed');
}
// Step 2: Install OpenZiti if needed
$this->reportProgress('INSTALL', 'Installing OpenZiti CLI...', 20);
if (!$this->installZiti()) {
throw new Exception('OpenZiti installation failed');
}
// Step 3: Create directories
$this->reportProgress('DIRECTORIES', 'Creating necessary directories...', 30);
if (!$this->createDirectories()) {
throw new Exception('Failed to create directories');
}
// Step 4: Register router with API
$this->reportProgress('REGISTER', 'Registering router with ZitiNexus Portal...', 40);
$result = $this->apiClient->registerRouter($hashKey);
if (!$result['success']) {
throw new Exception('Router registration failed: ' . $result['error']);
}
$this->routerData = $result['data'];
$this->reportProgress('REGISTER', 'Router registered successfully: ' . $this->routerData['routerInfo']['name'], 50);
// Step 5: Save configuration files
$this->reportProgress('CONFIG', 'Saving configuration files...', 60);
if (!$this->saveConfiguration()) {
throw new Exception('Failed to save configuration files');
}
// Step 6: Enroll router with OpenZiti
$this->reportProgress('ENROLL', 'Enrolling router with OpenZiti controller...', 70);
if (!$this->enrollWithZiti()) {
throw new Exception('Router enrollment with OpenZiti failed');
}
// Step 7: Create systemd service
$this->reportProgress('SERVICE', 'Creating systemd service...', 80);
if (!$this->createSystemdService()) {
throw new Exception('Failed to create systemd service');
}
// Step 8: Start router service
$this->reportProgress('START', 'Starting router service...', 90);
if (!$this->startRouter()) {
throw new Exception('Failed to start router service');
}
// Step 9: Report success status
$this->reportProgress('REPORT', 'Reporting enrollment status...', 95);
$this->reportSuccessStatus($hashKey);
$this->reportProgress('COMPLETE', 'Router enrollment completed successfully!', 100);
return [
'success' => true,
'routerName' => $this->routerData['routerInfo']['name'],
'routerId' => $this->routerData['routerInfo']['id'],
'message' => 'Router enrollment completed successfully'
];
} catch (Exception $e) {
$errorMsg = $e->getMessage();
logMessage('ERROR', $errorMsg);
$this->reportProgress('ERROR', $errorMsg, null);
// Report failure status
if (!empty($hashKey) && !empty($this->routerData['callbackUrl'])) {
$this->apiClient->reportStatus(
$this->routerData['callbackUrl'],
$hashKey,
'failed',
null,
$errorMsg
);
}
return [
'success' => false,
'error' => $errorMsg
];
}
}
/**
* Check system requirements
*/
private function checkSystemRequirements() {
// Check if running as root
if (!isRunningAsRoot()) {
throw new Exception('This script must be run as root (use sudo)');
}
// Check if curl is available
if (!$this->checkCommand('curl')) {
$this->reportProgress('REQUIREMENTS', 'Installing curl...');
if (!$this->installPackage('curl')) {
return false;
}
}
// Check if jq is available
if (!$this->checkCommand('jq')) {
$this->reportProgress('REQUIREMENTS', 'Installing jq...');
if (!$this->installPackage('jq')) {
return false;
}
}
// Check if systemctl is available
if (!$this->checkCommand('systemctl')) {
throw new Exception('systemctl is required but not available');
}
return true;
}
/**
* Install OpenZiti CLI
*/
private function installZiti() {
// Check if ziti is already installed
if ($this->checkCommand('ziti')) {
$output = '';
executeCommand('ziti version 2>/dev/null | head -n1', $output);
$this->reportProgress('INSTALL', 'OpenZiti CLI already installed: ' . trim($output));
return true;
}
$this->reportProgress('INSTALL', 'Setting up OpenZiti package repository...');
// Add GPG key
$gpgCommand = 'curl -sSLf https://get.openziti.io/tun/package-repos.gpg | gpg --dearmor --output /usr/share/keyrings/openziti.gpg';
if (!executeCommand($gpgCommand)) {
throw new Exception('Failed to add OpenZiti GPG key');
}
// Set proper permissions
if (!executeCommand('chmod a+r /usr/share/keyrings/openziti.gpg')) {
throw new Exception('Failed to set GPG key permissions');
}
// Add repository to sources list
$repoContent = 'deb [signed-by=/usr/share/keyrings/openziti.gpg] https://packages.openziti.org/zitipax-openziti-deb-stable debian main';
$tempFile = tempnam(sys_get_temp_dir(), 'openziti-repo');
file_put_contents($tempFile, $repoContent);
if (!executeCommand("cp '$tempFile' /etc/apt/sources.list.d/openziti-release.list")) {
unlink($tempFile);
throw new Exception('Failed to add OpenZiti repository');
}
unlink($tempFile);
// Update package list
$this->reportProgress('INSTALL', 'Updating package list...');
if (!executeCommand('apt-get update')) {
throw new Exception('Failed to update package list');
}
// Install openziti-router package
$this->reportProgress('INSTALL', 'Installing openziti-router package...');
if (!executeCommand('apt-get install -y openziti-router')) {
$this->reportProgress('INSTALL', 'Trying to install ziti CLI only...');
if (!executeCommand('apt-get install -y ziti')) {
throw new Exception('Failed to install OpenZiti CLI');
}
}
// Verify installation
if (!$this->checkCommand('ziti')) {
throw new Exception('OpenZiti CLI installation failed - command not found after installation');
}
$output = '';
executeCommand('ziti version 2>/dev/null | head -n1', $output);
$this->reportProgress('INSTALL', 'OpenZiti CLI installed successfully: ' . trim($output));
return true;
}
/**
* Create necessary directories
*/
private function createDirectories() {
$directories = [
CONFIG_DIR => 0755,
CERTS_DIR => 0700,
dirname(LOG_FILE) => 0755
];
foreach ($directories as $dir => $permissions) {
if (!is_dir($dir)) {
// Use sudo to create system directories
if (!executeCommand("mkdir -p '$dir'")) {
throw new Exception("Failed to create directory: $dir");
}
if (!executeCommand("chmod " . decoct($permissions) . " '$dir'")) {
throw new Exception("Failed to set permissions for directory: $dir");
}
} else {
// Ensure permissions are correct even if directory exists
executeCommand("chmod " . decoct($permissions) . " '$dir'");
}
}
return true;
}
/**
* Save configuration files
*/
private function saveConfiguration() {
// Save JWT using temp file and sudo
$tempJwtFile = tempnam(sys_get_temp_dir(), 'ziti-jwt');
file_put_contents($tempJwtFile, $this->routerData['jwt']);
if (!executeCommand("cp '$tempJwtFile' " . JWT_FILE)) {
unlink($tempJwtFile);
throw new Exception('Failed to save JWT file');
}
unlink($tempJwtFile);
if (!executeCommand("chmod 600 " . JWT_FILE)) {
throw new Exception('Failed to set JWT file permissions');
}
// Save router configuration using temp file and sudo
$tempConfigFile = tempnam(sys_get_temp_dir(), 'ziti-config');
file_put_contents($tempConfigFile, $this->routerData['routerConfig']['yaml']);
if (!executeCommand("cp '$tempConfigFile' " . ROUTER_CONFIG)) {
unlink($tempConfigFile);
throw new Exception('Failed to save router configuration');
}
unlink($tempConfigFile);
if (!executeCommand("chmod 644 " . ROUTER_CONFIG)) {
throw new Exception('Failed to set router config permissions');
}
// Fix router configuration for proper enrollment
$this->fixRouterConfiguration();
return true;
}
/**
* Fix router configuration (replicate bash script logic)
*/
private function fixRouterConfiguration() {
// Create backup using sudo
executeCommand("cp " . ROUTER_CONFIG . " " . ROUTER_CONFIG . ".backup");
$routerName = $this->routerData['routerInfo']['name'];
$routerId = $this->routerData['routerInfo']['id'];
$tenantId = $this->routerData['metadata']['tenantId'];
$controllerEndpoint = $this->routerData['metadata']['controllerEndpoint'] ?? 'enroll.zitinexus.com:443';
// Add tls: prefix if not present
if (strpos($controllerEndpoint, 'tls:') !== 0) {
$controllerEndpoint = 'tls:' . $controllerEndpoint;
}
// Build role attributes
$roleAttributesSection = '# No role attributes specified';
if (!empty($this->routerData['routerInfo']['roleAttributes'])) {
$roleAttributesSection = "roleAttributes:";
foreach ($this->routerData['routerInfo']['roleAttributes'] as $attr) {
$roleAttributesSection .= "\n - \"$attr\"";
}
}
$generatedAt = date('c');
$configContent = <<<EOF
v: 3
identity:
cert: /etc/zitirouter/certs/$routerName.cert
server_cert: /etc/zitirouter/certs/$routerName.server.chain.cert
key: /etc/zitirouter/certs/$routerName.key
ca: /etc/zitirouter/certs/$routerName.cas
ctrl:
endpoint: $controllerEndpoint
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
- $routerName
ip:
- "127.0.0.1"
- "::1"
# Tenant-specific role attributes
$roleAttributesSection
# Router metadata
metadata:
tenantId: "$tenantId"
zitiRouterId: "$routerId"
routerType: "private-edge"
generatedAt: "$generatedAt"
generatedBy: "ZitiNexus"
EOF;
// Write updated config using temp file and sudo
$tempConfigFile = tempnam(sys_get_temp_dir(), 'ziti-fixed-config');
file_put_contents($tempConfigFile, $configContent);
if (!executeCommand("cp '$tempConfigFile' " . ROUTER_CONFIG)) {
unlink($tempConfigFile);
throw new Exception('Failed to save updated router configuration');
}
unlink($tempConfigFile);
if (!executeCommand("chmod 644 " . ROUTER_CONFIG)) {
throw new Exception('Failed to set updated router config permissions');
}
}
/**
* Enroll router with OpenZiti
*/
private function enrollWithZiti() {
$command = 'ziti router enroll --jwt ' . JWT_FILE . ' ' . ROUTER_CONFIG . ' 2>&1';
$output = '';
if (!executeCommand($command, $output)) {
throw new Exception('Router enrollment failed: ' . $output);
}
// Verify certificates were created using sudo (since certs are root-owned with 600 permissions)
$routerName = $this->routerData['routerInfo']['name'];
$certFile = CERTS_DIR . '/' . $routerName . '.cert';
// Use sudo to check if certificate file exists (www-data can't read root-owned 600 files)
$checkOutput = '';
if (!executeCommand("test -f '$certFile'", $checkOutput)) {
// List what files actually exist for debugging
$listOutput = '';
executeCommand("ls -la " . CERTS_DIR . "/", $listOutput);
throw new Exception("Router certificate not found after enrollment. Expected: $certFile. Files in certs directory: " . $listOutput);
}
return true;
}
/**
* Create systemd service
*/
private function createSystemdService() {
$finalConfig = '/etc/zitirouter/zitirouter.yaml';
// Copy router config to final location using sudo
if (!executeCommand("cp " . ROUTER_CONFIG . " '$finalConfig'")) {
throw new Exception('Failed to copy router config to final location');
}
if (!executeCommand("chmod 644 '$finalConfig'")) {
throw new Exception('Failed to set final config permissions');
}
$serviceContent = <<<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;
// Write service file using sudo
$tempFile = tempnam(sys_get_temp_dir(), 'ziti-service');
file_put_contents($tempFile, $serviceContent);
if (!executeCommand("cp '$tempFile' " . SYSTEMD_SERVICE_FILE)) {
unlink($tempFile);
throw new Exception('Failed to create systemd service file');
}
unlink($tempFile);
// Reload systemd and enable service
if (!executeCommand('systemctl daemon-reload')) {
throw new Exception('Failed to reload systemd');
}
if (!executeCommand('systemctl enable ziti-router.service')) {
throw new Exception('Failed to enable ziti-router service');
}
return true;
}
/**
* Start router service
*/
private function startRouter() {
if (!executeCommand('systemctl start ziti-router.service')) {
throw new Exception('Failed to start router service');
}
// Wait and check status
sleep(3);
$output = '';
executeCommand('systemctl is-active ziti-router.service', $output);
if (trim($output) !== 'active') {
logMessage('WARNING', 'Router service may not be running properly');
}
return true;
}
/**
* Report success status
*/
private function reportSuccessStatus($hashKey) {
if (empty($this->routerData['callbackUrl'])) {
return;
}
$hostname = '';
$arch = '';
$os = '';
$zitiVersion = '';
executeCommand('hostname', $hostname);
executeCommand('uname -m', $arch);
executeCommand('lsb_release -d 2>/dev/null | cut -f2', $os);
executeCommand('ziti version 2>/dev/null | head -n1', $zitiVersion);
$routerInfo = [
'version' => trim($zitiVersion) ?: 'unknown',
'hostname' => trim($hostname),
'arch' => trim($arch),
'os' => trim($os) ?: 'Linux'
];
$this->apiClient->reportStatus(
$this->routerData['callbackUrl'],
$hashKey,
'success',
$routerInfo
);
}
/**
* Check if command exists
*/
private function checkCommand($command) {
$output = '';
return executeCommand("which $command", $output);
}
/**
* Install package using apt
*/
private function installPackage($package) {
return executeCommand("apt-get update && apt-get install -y $package");
}
/**
* Get system status information
*/
public function getSystemStatus() {
$status = [
'hostname' => '',
'ziti_status' => 'unknown',
'ziti_version' => '',
'service_active' => false,
'config_exists' => false,
'certificates_exist' => false
];
// Get hostname
executeCommand('hostname', $status['hostname']);
$status['hostname'] = trim($status['hostname']);
// Check if ziti command exists and get version
if ($this->checkCommand('ziti')) {
executeCommand('ziti version 2>/dev/null | head -n1', $status['ziti_version']);
$status['ziti_version'] = trim($status['ziti_version']);
$status['ziti_status'] = 'installed';
} else {
$status['ziti_status'] = 'not_installed';
}
// Check service status
$output = '';
if (executeCommand('systemctl is-active ziti-router.service 2>/dev/null', $output)) {
$status['service_active'] = trim($output) === 'active';
}
// Check configuration files
$status['config_exists'] = file_exists(ROUTER_CONFIG);
// Check certificates
if (is_dir(CERTS_DIR)) {
$certFiles = glob(CERTS_DIR . '/*.cert');
$status['certificates_exist'] = !empty($certFiles);
}
return $status;
}
}
?>