kurulum3
This commit is contained in:
parent
ced2c9d61b
commit
7e222bacbf
462
backend/package-lock.json
generated
462
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,15 +11,19 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@prisma/adapter-pg": "^7.9.1",
|
||||||
|
"@prisma/client": "^7.9.1",
|
||||||
"axios": "^1.19.0",
|
"axios": "^1.19.0",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"pg": "^8.23.0"
|
"pg": "^8.23.0",
|
||||||
|
"ssh2": "^1.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@types/node": "^26.2.0",
|
"@types/node": "^26.2.0",
|
||||||
|
"@types/ssh2": "^1.15.5",
|
||||||
"nodemon": "^3.1.14",
|
"nodemon": "^3.1.14",
|
||||||
"prisma": "^7.9.1",
|
"prisma": "^7.9.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|||||||
73
backend/simulate.js
Normal file
73
backend/simulate.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const { Pool } = require('pg');
|
||||||
|
const { PrismaPg } = require('@prisma/adapter-pg');
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
|
||||||
|
const pool = new Pool({ connectionString: 'postgresql://saas_user:saas_secure_pass_2026@10.0.1.151:5432/saas_5651?schema=public' });
|
||||||
|
const adapter = new PrismaPg(pool);
|
||||||
|
const prisma = new PrismaClient({ adapter });
|
||||||
|
|
||||||
|
const host = '10.0.1.150';
|
||||||
|
const password = '02!Aug2013';
|
||||||
|
|
||||||
|
async function generateDeviceVpnConfig(macAddress, vpnIp) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const conn = new Client();
|
||||||
|
const clientName = `device_${macAddress.replace(/:/g, '')}`;
|
||||||
|
|
||||||
|
const setupScript = `
|
||||||
|
#!/bin/bash
|
||||||
|
cd /etc/openvpn/server
|
||||||
|
|
||||||
|
# 1. Create client certificate
|
||||||
|
if [ ! -f "easy-rsa/pki/issued/${clientName}.crt" ]; then
|
||||||
|
cd easy-rsa
|
||||||
|
./easyrsa --batch build-client-full "${clientName}" nopass
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Assign static IP
|
||||||
|
mkdir -p ccd
|
||||||
|
echo "ifconfig-push ${vpnIp} 255.255.255.0" > "ccd/${clientName}"
|
||||||
|
|
||||||
|
echo "SUCCESS: VPN Config generated for ${clientName} with IP ${vpnIp}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(setupScript, (err, stream) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
stream.on('close', (code) => {
|
||||||
|
conn.end();
|
||||||
|
if (code === 0) resolve(true);
|
||||||
|
else reject(new Error('Script failed with code ' + code));
|
||||||
|
}).on('data', data => console.log('STDOUT: ' + data))
|
||||||
|
.stderr.on('data', data => console.error('STDERR: ' + data));
|
||||||
|
});
|
||||||
|
}).connect({ host, port: 22, username: 'root', password, readyTimeout: 10000 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runTest() {
|
||||||
|
console.log('--- Starting Test: SaaS E2E Tenant & Device Creation ---');
|
||||||
|
|
||||||
|
console.log('1. Creating new Tenant: "Test Hotel A.S."');
|
||||||
|
const tenant = await prisma.tenant.create({ data: { name: 'Test Hotel A.S.' } });
|
||||||
|
|
||||||
|
console.log('2. Allocated VPN IP for new device: 10.10.0.1');
|
||||||
|
const mac = '00:11:22:33:44:55';
|
||||||
|
await prisma.device.create({
|
||||||
|
data: {
|
||||||
|
tenantId: tenant.id,
|
||||||
|
name: 'Test Hotel Gateway',
|
||||||
|
macAddress: mac,
|
||||||
|
vpnIp: '10.10.0.1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('3. Connecting to VPN Server (10.0.1.150) to generate config...');
|
||||||
|
await generateDeviceVpnConfig(mac, '10.10.0.1');
|
||||||
|
|
||||||
|
console.log('--- Test Completed ---');
|
||||||
|
}
|
||||||
|
|
||||||
|
runTest().catch(console.error).finally(() => prisma.$disconnect());
|
||||||
59
backend/simulate.ts
Normal file
59
backend/simulate.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { VpnService } from './src/services/VpnService';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
const vpnService = new VpnService();
|
||||||
|
|
||||||
|
async function runTest() {
|
||||||
|
console.log('--- Starting Test: SaaS E2E Tenant & Device Creation ---');
|
||||||
|
|
||||||
|
// 1. Create a Tenant
|
||||||
|
console.log('1. Creating new Tenant: "Test Hotel A.S."');
|
||||||
|
const tenant = await prisma.tenant.create({
|
||||||
|
data: { name: 'Test Hotel A.S.' }
|
||||||
|
});
|
||||||
|
console.log(` Tenant created with ID: ${tenant.id}`);
|
||||||
|
|
||||||
|
// 2. Figure out VPN IP
|
||||||
|
const baseIp = '10.10.0.';
|
||||||
|
const lastDevice = await prisma.device.findFirst({
|
||||||
|
where: { vpnIp: { startsWith: baseIp } },
|
||||||
|
orderBy: { vpnIp: 'desc' }
|
||||||
|
});
|
||||||
|
|
||||||
|
let nextVpnIp = '10.10.0.1';
|
||||||
|
if (lastDevice && lastDevice.vpnIp) {
|
||||||
|
const lastOctet = parseInt(lastDevice.vpnIp.split('.')[3], 10);
|
||||||
|
nextVpnIp = `${baseIp}${lastOctet + 1}`;
|
||||||
|
}
|
||||||
|
console.log(`2. Allocated VPN IP for new device: ${nextVpnIp}`);
|
||||||
|
|
||||||
|
// 3. Create Device
|
||||||
|
console.log('3. Registering OPNsense device...');
|
||||||
|
const mac = '00:11:22:33:44:55';
|
||||||
|
const device = await prisma.device.create({
|
||||||
|
data: {
|
||||||
|
tenantId: tenant.id,
|
||||||
|
name: 'Test Hotel Gateway',
|
||||||
|
macAddress: mac,
|
||||||
|
vpnIp: nextVpnIp,
|
||||||
|
opnsenseUrl: 'https://192.168.1.1',
|
||||||
|
apiKey: 'testkey',
|
||||||
|
apiSecret: 'testsecret'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(` Device registered successfully with MAC: ${mac}`);
|
||||||
|
|
||||||
|
// 4. Trigger VPN Generation
|
||||||
|
console.log('4. Connecting to VPN Server (10.0.1.150) via SSH to generate .ovpn config...');
|
||||||
|
try {
|
||||||
|
await vpnService.generateDeviceVpnConfig(mac, nextVpnIp);
|
||||||
|
console.log(' SUCCESS! .ovpn file and ccd config created on VPN server.');
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(' FAILED to generate VPN config:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('--- Test Completed ---');
|
||||||
|
}
|
||||||
|
|
||||||
|
runTest().catch(e => console.error(e)).finally(() => prisma.$disconnect());
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { VpnService } from '../services/VpnService';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
const vpnService = new VpnService();
|
||||||
|
|
||||||
// Helper to generate a VPN IP for a new device
|
// Helper to generate a VPN IP for a new device
|
||||||
async function getNextVpnIp(): Promise<string> {
|
async function getNextVpnIp(): Promise<string> {
|
||||||
@ -55,8 +57,14 @@ router.post('/', async (req, res) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Actually trigger OpenVPN config generation via SSH or scripts to the VPN Server (10.0.1.150)
|
// Trigger OpenVPN config generation via SSH to the VPN Server (10.0.1.150)
|
||||||
// generateVpnConfig(nextVpnIp, macAddress);
|
try {
|
||||||
|
await vpnService.generateDeviceVpnConfig(macAddress, nextVpnIp);
|
||||||
|
console.log(`Successfully generated VPN config for ${macAddress} at ${nextVpnIp}`);
|
||||||
|
} catch (vpnErr) {
|
||||||
|
console.error('Failed to generate VPN config:', vpnErr);
|
||||||
|
// We don't fail the API request entirely, but log the error
|
||||||
|
}
|
||||||
|
|
||||||
res.status(201).json(newDevice);
|
res.status(201).json(newDevice);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
88
backend/src/services/VpnService.js
Normal file
88
backend/src/services/VpnService.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Client } from 'ssh2';
|
||||||
|
export class VpnService {
|
||||||
|
host;
|
||||||
|
password;
|
||||||
|
constructor() {
|
||||||
|
this.host = '10.0.1.150'; // VPN Server IP
|
||||||
|
this.password = '02!Aug2013'; // Root password
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Automatically generates an OpenVPN client config on the VPN server
|
||||||
|
* and allocates the static VPN IP in the OpenVPN ccd directory.
|
||||||
|
*/
|
||||||
|
async generateDeviceVpnConfig(macAddress, vpnIp) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const conn = new Client();
|
||||||
|
// Clean up the MAC address to use as a client name (remove colons)
|
||||||
|
const clientName = `device_${macAddress.replace(/:/g, '')}`;
|
||||||
|
// The shell script to run on the VPN Server (10.0.1.150)
|
||||||
|
const setupScript = `
|
||||||
|
#!/bin/bash
|
||||||
|
cd /etc/openvpn/server
|
||||||
|
|
||||||
|
# 1. Create client certificate if it doesn't exist
|
||||||
|
if [ ! -f "easy-rsa/pki/issued/\${clientName}.crt" ]; then
|
||||||
|
cd easy-rsa
|
||||||
|
./easyrsa build-client-full "\${clientName}" nopass
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Assign static IP in ccd (Client Config Directory)
|
||||||
|
mkdir -p ccd
|
||||||
|
echo "ifconfig-push \${vpnIp} 255.255.255.0" > "ccd/\${clientName}"
|
||||||
|
|
||||||
|
# 3. Generate client .ovpn file
|
||||||
|
cat <<EOF > "/root/\${clientName}.ovpn"
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto tcp
|
||||||
|
remote 10.0.1.150 1194
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
ca ca.crt
|
||||||
|
cert \${clientName}.crt
|
||||||
|
key \${clientName}.key
|
||||||
|
remote-cert-tls server
|
||||||
|
cipher AES-256-CBC
|
||||||
|
verb 3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "SUCCESS: VPN Config generated for \${clientName} with IP \${vpnIp}"
|
||||||
|
`;
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(setupScript, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
conn.end();
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
let output = '';
|
||||||
|
stream.on('close', (code, signal) => {
|
||||||
|
conn.end();
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(new Error(`VPN Script failed with code ${code}. Output: ${output}`));
|
||||||
|
}
|
||||||
|
}).on('data', (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
console.log('STDOUT: ' + data);
|
||||||
|
}).stderr.on('data', (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
console.error('STDERR: ' + data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
}).connect({
|
||||||
|
host: this.host,
|
||||||
|
port: 22,
|
||||||
|
username: 'root',
|
||||||
|
password: this.password,
|
||||||
|
readyTimeout: 10000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
94
backend/src/services/VpnService.ts
Normal file
94
backend/src/services/VpnService.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { Client } from 'ssh2';
|
||||||
|
|
||||||
|
export class VpnService {
|
||||||
|
private host: string;
|
||||||
|
private password: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.host = '10.0.1.150'; // VPN Server IP
|
||||||
|
this.password = '02!Aug2013'; // Root password
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically generates an OpenVPN client config on the VPN server
|
||||||
|
* and allocates the static VPN IP in the OpenVPN ccd directory.
|
||||||
|
*/
|
||||||
|
async generateDeviceVpnConfig(macAddress: string, vpnIp: string): Promise<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const conn = new Client();
|
||||||
|
|
||||||
|
// Clean up the MAC address to use as a client name (remove colons)
|
||||||
|
const clientName = `device_${macAddress.replace(/:/g, '')}`;
|
||||||
|
|
||||||
|
// The shell script to run on the VPN Server (10.0.1.150)
|
||||||
|
const setupScript = `
|
||||||
|
#!/bin/bash
|
||||||
|
cd /etc/openvpn/server
|
||||||
|
|
||||||
|
# 1. Create client certificate if it doesn't exist
|
||||||
|
if [ ! -f "easy-rsa/pki/issued/\${clientName}.crt" ]; then
|
||||||
|
cd easy-rsa
|
||||||
|
./easyrsa build-client-full "\${clientName}" nopass
|
||||||
|
cd ..
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Assign static IP in ccd (Client Config Directory)
|
||||||
|
mkdir -p ccd
|
||||||
|
echo "ifconfig-push \${vpnIp} 255.255.255.0" > "ccd/\${clientName}"
|
||||||
|
|
||||||
|
# 3. Generate client .ovpn file
|
||||||
|
cat <<EOF > "/root/\${clientName}.ovpn"
|
||||||
|
client
|
||||||
|
dev tun
|
||||||
|
proto tcp
|
||||||
|
remote 10.0.1.150 1194
|
||||||
|
resolv-retry infinite
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
ca ca.crt
|
||||||
|
cert \${clientName}.crt
|
||||||
|
key \${clientName}.key
|
||||||
|
remote-cert-tls server
|
||||||
|
cipher AES-256-CBC
|
||||||
|
verb 3
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "SUCCESS: VPN Config generated for \${clientName} with IP \${vpnIp}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
conn.on('ready', () => {
|
||||||
|
conn.exec(setupScript, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
conn.end();
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
stream.on('close', (code: any, signal: any) => {
|
||||||
|
conn.end();
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`VPN Script failed with code ${code}. Output: ${output}`));
|
||||||
|
}
|
||||||
|
}).on('data', (data: any) => {
|
||||||
|
output += data.toString();
|
||||||
|
console.log('STDOUT: ' + data);
|
||||||
|
}).stderr.on('data', (data: any) => {
|
||||||
|
output += data.toString();
|
||||||
|
console.error('STDERR: ' + data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
}).connect({
|
||||||
|
host: this.host,
|
||||||
|
port: 22,
|
||||||
|
username: 'root',
|
||||||
|
password: this.password,
|
||||||
|
readyTimeout: 10000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
44
backend/tsconfig.json
Normal file
44
backend/tsconfig.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
|
"compilerOptions": {
|
||||||
|
// File Layout
|
||||||
|
// "rootDir": "./src",
|
||||||
|
// "outDir": "./dist",
|
||||||
|
|
||||||
|
// Environment Settings
|
||||||
|
// See also https://aka.ms/tsconfig/module
|
||||||
|
"module": "nodenext",
|
||||||
|
"target": "esnext",
|
||||||
|
"types": [],
|
||||||
|
// For nodejs:
|
||||||
|
// "lib": ["esnext"],
|
||||||
|
// "types": ["node"],
|
||||||
|
// and npm install -D @types/node
|
||||||
|
|
||||||
|
// Other Outputs
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
|
||||||
|
// Stricter Typechecking Options
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
|
||||||
|
// Style Options
|
||||||
|
// "noImplicitReturns": true,
|
||||||
|
// "noImplicitOverride": true,
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noUnusedParameters": true,
|
||||||
|
// "noFallthroughCasesInSwitch": true,
|
||||||
|
// "noPropertyAccessFromIndexSignature": true,
|
||||||
|
|
||||||
|
// Recommended Options
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user