Skip to content

Commit

Permalink
New features
Browse files Browse the repository at this point in the history
### UPDATED
- Only command responses will now be converted to a JSON string then logged.

### ADDED
- Wake-on-LAN device check feature.
  • Loading branch information
mrjackyliang committed Sep 7, 2024
1 parent 27e396c commit a306c82
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 14 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ services:
| `reload-filter` | Reload the filters on your pfSense® firewall | `GET` | [Reload Filter Endpoint](#reload-filter-endpoint) |
| `update-dyndns` | Update the Dynamic DNS settings on your pfSense® firewall | `GET` | [Update Dynamic DNS Endpoint](#update-dynamic-dns-endpoint) |
| `wol` | Send a Wake-on-LAN request to a connected device | `POST` | [Wake-on-LAN Endpoint](#wake-on-lan-endpoint) |
| `wol-check` | Send a Wake-on-LAN check request to a connected device | `POST` | [Wake-on-LAN Check Endpoint](#wake-on-lan-check-endpoint) |

## Authorizing Your Requests
When accessing the controller, ensure that you have the `Authorization` header properly set to the `API_KEY` you specified during image creation. For example:
Expand Down Expand Up @@ -101,11 +102,13 @@ This section describes how to interact with the available API endpoints for mana
GET http://localhost:9898/reload-filter
Authorization: Bearer [YOUR_API_KEY]
```

#### Update Dynamic DNS Endpoint:
```http request
GET http://localhost:9898/update-dyndns
Authorization: Bearer [YOUR_API_KEY]
```

#### Wake-on-LAN Endpoint:
```http request
POST http://localhost:9898/wol
Expand All @@ -118,6 +121,17 @@ Content-Type: application/json
}
```

#### Wake-on-LAN Check Endpoint:
```http request
POST http://localhost:9898/wol-check
Authorization: Bearer [YOUR_API_KEY]
Content-Type: application/json
{
"ipAddress": "[YOUR_IP_ADDRESS]",
}
```

## Retrieving the Broadcast IP Address
In order to send a Wake-on-LAN request, a broadcast address is required since magic packets cannot be sent across subnets. Follow the instructions below:

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pfsense-actions",
"displayName": "pfSense® Actions",
"version": "1.0.0",
"version": "1.1.0",
"description": "A controller that allows a network administrator to run simple actions such as Wake-on-LAN and reload firewall filters",
"main": "./build/index.js",
"exports": "./build/index.js",
Expand Down
83 changes: 70 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from 'express';

import { Pfsense } from '@/lib/api.js';
import { wakeOnLan } from '@/lib/schema.js';
import { wakeOnLan, wakeOnLanCheck } from '@/lib/schema.js';
import { getEnvironmentVariables, isValidApiKey } from '@/lib/utility.js';
import type {
ServerAddMiddlewareReturns,
Expand All @@ -21,6 +21,9 @@ import type {
ServerRouteUpdateDyndnsRequest,
ServerRouteUpdateDyndnsResponse,
ServerRouteUpdateDyndnsReturns,
ServerRouteWolCheckRequest,
ServerRouteWolCheckResponse,
ServerRouteWolCheckReturns,
ServerRouteWolRequest,
ServerRouteWolResponse,
ServerRouteWolReturns,
Expand Down Expand Up @@ -99,6 +102,7 @@ class Server {
this.#app.get('/reload-filter', this.routeReloadFilter.bind(this));
this.#app.get('/update-dyndns', this.routeUpdateDyndns.bind(this));
this.#app.post('/wol', this.routeWol.bind(this));
this.#app.post('/wol-check', this.routeWolCheck.bind(this));
}

/**
Expand Down Expand Up @@ -156,9 +160,9 @@ class Server {
try {
const instance = new Pfsense(this.#env);

console.log(await instance.login());
console.log(await instance.systemInformation());
console.log(await instance.logout());
await instance.login();
console.log(JSON.stringify(await instance.systemInformation()));
await instance.logout();

response.sendStatus(200);
} catch (error) {
Expand All @@ -184,9 +188,9 @@ class Server {
try {
const instance = new Pfsense(this.#env);

console.log(await instance.login());
console.log(await instance.reloadFilter());
console.log(await instance.logout());
await instance.login();
console.log(JSON.stringify(await instance.reloadFilter()));
await instance.logout();

response.sendStatus(200);
} catch (error) {
Expand All @@ -212,9 +216,9 @@ class Server {
try {
const instance = new Pfsense(this.#env);

console.log(await instance.login());
console.log(await instance.updateDyndns());
console.log(await instance.logout());
await instance.login();
console.log(JSON.stringify(await instance.updateDyndns()));
await instance.logout();

response.sendStatus(200);
} catch (error) {
Expand Down Expand Up @@ -249,9 +253,9 @@ class Server {

const { broadcastAddress, macAddress } = responseBody.data;

console.log(await instance.login());
console.log(await instance.wakeOnLan(broadcastAddress, macAddress));
console.log(await instance.logout());
await instance.login();
console.log(JSON.stringify(await instance.wakeOnLan(broadcastAddress, macAddress)));
await instance.logout();

response.sendStatus(200);
} catch (error) {
Expand All @@ -260,6 +264,59 @@ class Server {
response.sendStatus(500);
}
}

/**
* Server - Route wol check.
*
* @param {ServerRouteWolCheckRequest} request - Request.
* @param {ServerRouteWolCheckResponse} response - Response.
*
* @private
*
* @returns {ServerRouteWolCheckReturns}
*
* @since 1.0.0
*/
private async routeWolCheck(request: ServerRouteWolCheckRequest, response: ServerRouteWolCheckResponse): ServerRouteWolCheckReturns {
try {
const instance = new Pfsense(this.#env);
const responseBody = wakeOnLanCheck.safeParse(request.body);

if (!responseBody.success) {
response.sendStatus(400);

return;
}

const { ipAddress } = responseBody.data;

await instance.login();

// Pings the device 3 times (5 tries each) until it gives up.
for (let i = 1; i <= 3; i += 1) {
const pingResponse = await instance.ping(ipAddress, 5);

console.log(JSON.stringify(pingResponse));

// If command does not respond with "100.0% packet loss", it means the device is now online.
if (pingResponse.success && !pingResponse.info.stdout.includes('100.0% packet loss')) {
await instance.logout();

response.sendStatus(200);

return;
}
}

await instance.logout();

response.sendStatus(503);
} catch (error) {
console.error(error);

response.sendStatus(500);
}
}
}

new Server();
48 changes: 48 additions & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import type {
PfsenseIsAuthenticatedReturns,
PfsenseLoginReturns,
PfsenseLogoutReturns,
PfsensePingCount,
PfsensePingIpAddress,
PfsensePingReturns,
PfsenseReloadFilterReturns,
PfsenseSession,
PfsenseSystemInformationReturns,
Expand Down Expand Up @@ -152,6 +155,51 @@ export class Pfsense {
};
}

/**
* Pfsense - Ping.
*
* @param {PfsensePingIpAddress} ipAddress - Ip address.
* @param {PfsensePingCount} count - Count.
*
* @returns {PfsensePingReturns}
*
* @since 1.0.0
*/
public async ping(ipAddress: PfsensePingIpAddress, count: PfsensePingCount): PfsensePingReturns {
let errorObject;

try {
// Check if this session is not authenticated.
if (!await this.isAuthenticated()) {
return {
action: 'PING',
success: false,
info: {
message: 'Failed to authenticate to pfSense via SSH',
},
};
}

const response = await this.#session.sshClient.execCommand(`ping -c ${count} ${ipAddress}`);

return {
action: 'PING',
success: true,
info: response,
};
} catch (error) {
errorObject = serializeError(error);
}

return {
action: 'PING',
success: false,
info: {
error: errorObject,
},
};
}

/**
* Pfsense - Reload filter.
*
Expand Down
9 changes: 9 additions & 0 deletions src/lib/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,12 @@ export const wakeOnLan = z.object({
},
),
});

/**
* Wake on lan check.
*
* @since 1.0.0
*/
export const wakeOnLanCheck = z.object({
ipAddress: z.string().ip(),
});
24 changes: 24 additions & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ export type PfsenseLogoutReturnsInfo = null;

export type PfsenseLogoutReturns = Promise<ApiResponse<'LOGOUT', PfsenseLogoutReturnsInfo>>;

/**
* Pfsense - Ping.
*
* @since 1.0.0
*/
export type PfsensePingIpAddress = string;

export type PfsensePingCount = number;

export type PfsensePingReturnsInfo = SSHExecCommandResponse;

export type PfsensePingReturns = Promise<ApiResponse<'PING', PfsensePingReturnsInfo>>;

/**
* Pfsense - Reload filter.
*
Expand Down Expand Up @@ -199,6 +212,17 @@ export type ServerRouteWolResponse = express.Response;

export type ServerRouteWolReturns = Promise<void>;

/**
* Server - Route wol check.
*
* @since 1.0.0
*/
export type ServerRouteWolCheckRequest = express.Request;

export type ServerRouteWolCheckResponse = express.Response;

export type ServerRouteWolCheckReturns = Promise<void>;

/**
* Server - Start server.
*
Expand Down
1 change: 1 addition & 0 deletions src/types/shared.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { ErrorObject } from 'serialize-error';
export type ApiResponseAction =
'LOGIN'
| 'LOGOUT'
| 'PING'
| 'RELOAD_FILTER'
| 'SYSTEM_INFORMATION'
| 'UPDATE_DYNDNS'
Expand Down

0 comments on commit a306c82

Please sign in to comment.