In the modern world, there are different approaches to run scripts using WebHooks, such as Azure Functions, Azure Automation, or Amazon Lambda. In the on-prem world, users might use tools like Jenkins just to provide the ability to trigger function execution, which can be overkill for webhooks. This small lightweight project was born as an open-source alternative that you can easily deploy on both Linux or Windows boxes.
Users send an HTTP GET or POST message to the server. The call includes the following parameters:
- Security Key
- Script name
- [Optional] Param
The base project URI is:
https://localhost:5001/webhook/v1?key=yourKey&script=YourScript¶m=-Your-Params
To protect scripts from accidental or unauthorized executions, the server will load the Key from appsettings.json
and compare it with the one from the user request. If they don't match, the server will stop executing the pipeline and return an error:
If any exceptions arise along the way, the global exception handler will return an error:
Here is an example of a successful webhook without params:
https://localhost:5001/webhook/v1?key=24ffc5be-7dd8-479f-898e-27169bf23e7f&script=Test-Script.ps1
And with parameters:
https://localhost:5001/webhook/v1?key=24ffc5be-7dd8-479f-898e-27169bf23e7f&script=Test-Script.ps1¶m=-Param1-A-Param2-B
This example is different only in terms how you supply the data. Here you can see that the http body is used.
Optionally you can specify a trigger for your script. The trigger acts as a filter or the set of rules for the script. Available triggers:
- httpMethod
- ipAddresses
- timeFrame
You can set certain script to be executed only from POST
or GET
method.
This parameter is optional. If not set then you can run this script from both methods.
Add IP address to the array if you need to restrict the script to run only from the certain caller IP address. Both IPv4 and IPv6 are supported. Optional parameter. If not provided then any IP address can call the script.
You can specify one or more time frames when you have a time constrain.
If set then the script can be launched only on specified time range[s].
Time format HH:mm:ss
in UTC.
Optional parameter. If not set then the script can be launched anytime of the day.
In this example script test-script.py
can be executed only from GET
method and only if the caller is in the ipAddresses
list.
"ScriptsMapping": [
{
"name": "test-script.py",
"key": "77aae8aa-50d2-49d9-be8c-e9f59aaf39e9",
"trigger": {
"httpMethod": "GET",
"ipAddresses": [
"127.0.0.1",
"::1"
],
"timeFrames":
[
{
"startUtc": "03:00:00",
"endUtc": "07:59:59"
},
{
"startUtc": "23:00:00",
"endUtc": "00:59:59"
}
]
}
}
]
You can find two default script handlers in the configuration:
- Python3
- Pwsh (Powershell)
If you keep them as they are, you need to install pwsh and python and add them to the PATH
variable (default for Windows) on the server where this API will be running.
There are a few places where you can define the security key:
- You can provide a key for each script. In this case, you don't need to share a common one.
- Per script handler. All scripts registered under the handler will share the same key if they don't have a unique key (check option 1).
- Global key (or Default). This key will be used
if the script or handler don't have keys specified.
For example:
"Scripts": {
"DefaultKey": "24ffc5be-7dd8-479f-898e-27169bf23e7f",
"Handlers": [
{
"ProcessName": "pwsh",
"ScriptsLocation": "./powershellscripts",
"FileExtension": "ps1",
"Key": "88aae8aa-50d2-49d9-be8c-e9f59aaf3988"
},
{
"ProcessName": "python3",
"ScriptsLocation": "./pythonscripts",
"FileExtension": "py",
"ScriptsMapping": [
{
"name": "test-script.py",
"key": "77aae8aa-50d2-49d9-be8c-e9f59aaf39e9",
"trigger": {
"httpMethod": "GET",
"ipAddresses": [
"127.0.0.1",
"::1"
]
}
}
]
}
]
}
In this example, the script test-script.py
has a unique key 77aae8aa-50d2-49d9-be8c-e9f59aaf39e9
. The pwsh handler has a key that will be valid for all scripts that don't have a unique key set.
This app was built as a cross-platform and can be installed on many well-known platforms such as Mac, Windows, and Linux. There are multiple ways of running this app on the server, but let's concentrate on two major approaches.
The biggest advantage of the framework-dependent version is a smaller app footprint. This app does not include a runtime and hence is lightweight, consuming only 100-200KB of storage. It is also easier to distribute because the same executable can be run on different platforms.
To use this approach, you need to install the .NET 6.0 runtime on the server. You can download and follow the steps outlined on this page.
This is a self-contained package that includes a runtime and can be executed on the server without installing dependencies. Because it includes a runtime that is specific to the operating system (and architecture), you cannot run a Windows build on Linux and vice versa.
Also, the build will be heavier than the framework-dependent version.
I have a TODO item to simplify the release process and publish binaries for all platforms together. Currently, you can find published binaries here.
Each platform will have a dedicated release. For example, Release 14
will include:
- Release 14 for Windows
- Release 14 for RedHat
- Release 14 for Linux
- Release 14 (Requires .NET 6.0 runtime)
I strongly recommend setting up an HTTPS listener and keeping it as the only available entry point.
You can run this API as a service or put it behind the proxy/web server. For instance IIS, Apache, NGINX.
By default, I've included one ps1 script Test-Script.ps1
that you can run. The script has 3 optional parameters:
-Param1 [string]
-Param2 [string]
If you provide -Param1 A -Param2 B
as parameters, the script will return them (as shown in the screenshot above).
By default, the web app on Windows platforms uses the applicationPool
context (LocalSystem), which means that it has local admin access. You can provide any credentials inside your ps1 script by using your own logic, or you can change the appSetting username in IIS to change the context.
On linux it is up to you to choose the user context.
When you need to add a new script handler, you just need to modify appsettings.json
. You don't need to change the code