592 lines
20 KiB
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;
|
|
}
|
|
}
|
|
?>
|