Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 89926c7

Browse files
committedFeb 21, 2025
v0.5.12 update
added opentunnel and closetunnel commands for remote SSH port forwarding added webctrl.tunnels to store persistent SSH port forwarding tunnels added an optional SSH proxy connection added local hostname checking to prevent duplicate servers synced from clippings updated jsch dependency updated README
1 parent a52844e commit 89926c7

25 files changed

+998
-16
lines changed
 

‎README.md

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ WebCTRL is a trademark of Automated Logic Corporation. Any other trademarks men
2525
- [Operator Blacklist Exceptions](#operator-blacklist-exceptions)
2626
- [Pending Commands](#pending-commands)
2727
- [Examples](#examples)
28+
- [SSH Tunnels](#ssh-tunnels)
2829
- [Find Trends](#find-trends)
2930
- [Trend Mappings](#trend-mappings)
3031
- [Technical Information](#technical-information)
3132
- [Packaged Dependencies](#packaged-dependencies)
3233
- [Server ID Reset](#server-id-reset)
34+
- [Hostname Matching](#hostname-matching)
3335

3436
## Feature Summary
3537

@@ -50,7 +52,7 @@ When this WebCTRL add-on is installed and configured, it periodically communicat
5052
- Blacklist operators to delete them everywhere
5153
- Local operators (those that are not white or blacklisted) remain unaltered
5254
- WebCTRL's operator authentication provider is unaffected
53-
- For security, operator passwords hashed and salted using the same algorithm WebCTRL uses
55+
- For security, operator passwords are hashed and salted using the same algorithm WebCTRL uses
5456
- Add-on synchronization:
5557
- Synchronize a subnet of whitelisted add-ons between all connected servers
5658
- Deploy a new add-on everywhere in a single motion
@@ -61,6 +63,7 @@ When this WebCTRL add-on is installed and configured, it periodically communicat
6163
- Only whitelisted operators are permitted to manage the database
6264
- Remotely push out commands for servers to execute during their next sync
6365
- Among other things, this function can be used for automated bulk installation of WebCTRL patches
66+
- Configure reverse SSH tunnels for remote access of connected servers
6467
- Import or export a JSON document containing local operators
6568
- Optionally collect data from specific WebCTRL trends
6669

@@ -141,9 +144,13 @@ Fill out all the required fields, click *Save Changes* and then click *Sync Now*
141144

142145
When the add-on connects to the PostgreSQL database for the first time, it generates a unique server ID for itself. The *Reset ID* button can be used to delete the saved ID and generate a new one.
143146

147+
If firewall restrictions limit PostgreSQL traffic, you can try using an SSH proxy server as an intermediary. In the below example, you would connect to `127.0.0.1:5433/database` after the proxy is set. The proxy is activated only when the add-on tries to sync, and then is deactivated afterwards. For security and simplicity, proxy settings cannot be viewed from a web browser after they are set.
148+
149+
![](./resources/ssh_proxy.png)
150+
144151
### SFTP Server
145152

146-
Whitelisted add-ons must reside on an SFTP server. [JSch](https://github.com/mwiede/jsch) is a third-party library this add-on uses to establish SFTP connections. If you want to lock down an SSH daemon to serve SFTP only, refer to the following example snippet from *sshd_config*.
153+
Whitelisted add-ons must reside on an SFTP server. [JSch](https://github.com/mwiede/jsch) is a third-party library this add-on uses to establish SSH and SFTP connections. If you want to lock down an SSH daemon to serve SFTP only, refer to the following example snippet from [*sshd_config*](https://man.openbsd.org/sshd_config).
147154

148155
```
149156
Match group sftp
@@ -154,6 +161,23 @@ AllowTcpForwarding no
154161
ForceCommand internal-sftp
155162
```
156163

164+
If you want to support reverse SSH tunnels and a proxy for connecting to the PostgreSQL database, you might want to configure something like the following instead:
165+
166+
```
167+
Match group sftp
168+
ChrootDirectory /data/sftp
169+
GatewayPorts yes
170+
AllowTcpForwarding yes
171+
PermitOpen 127.0.0.1:5432
172+
AllowAgentForwarding no
173+
AllowStreamLocalForwarding no
174+
X11Forwarding no
175+
PermitTTY no
176+
ClientAliveInterval 60
177+
ClientAliveCountMax 3
178+
ForceCommand internal-sftp
179+
```
180+
157181
You'll need to configure [public-key authentication](https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server) for your SFTP user. Go to the settings page in the add-on and set `ftp_key` to the full private key your user needs for authentication. Set `ftp_host` to the hostname or IP address of the SFTP server. Also configure `ftp_port` and `ftp_username` appropriately. The [`ssh-keyscan`](https://man.openbsd.org/ssh-keyscan.1) command can be used to retrieve the data required to populate the `ftp_known_hosts` field.
158182

159183
![](./resources/sftp_config.png)
@@ -171,7 +195,7 @@ In addition to the SFTP connection settings shown in the previous section, there
171195
| `debug` | `false` | When enabled, log messages will be more verbose. |
172196
| `log_expiration` | `60` | Specifies how many days to retain log messages in the database. |
173197
| `auto_update` | `true` | Specifies whether to attempt automatic updates for this add-on. |
174-
| `version` | `0.5.10` | When `auto_update` is enabled, any connected client whose add-on version is less than this value will be updated. |
198+
| `version` | `0.5.12` | When `auto_update` is enabled, any connected client whose add-on version is less than this value will be updated. |
175199
| `download_path` | `/webctrl/addons/PostgreSQL_Connect.addon` | When `auto_update` is enabled, this is the SFTP server path where the latest version add-on file will be retrieved. |
176200
| `license_directory` | `/webctrl/licenses` | Specifies an SFTP server directory path for where to store WebCTRL license files. |
177201
| `ftp_host` | `postgresql.domain.com` | SFTP server hostname or IP address. |
@@ -193,7 +217,7 @@ You cannot use this mechanism to automatically downgrade the add-on to an earlie
193217

194218
## Synchronizaton
195219

196-
Now that your PostgreSQL database and SFTP server is configured, this section explains how to setup synchronization of operators, add-ons, and trends. We will go through each page accessible from the web UI.
220+
Now that your PostgreSQL database and SFTP server is configured, this section explains how to setup synchronization of operators, add-ons, reverse SSH tunnels, and trends. We will go through each page accessible from the web UI.
197221

198222
### Server List
199223

@@ -204,7 +228,7 @@ This page lists all connected servers. If a server is decomissioned or permanent
204228
| ID | `1` | Internal ID which uniquely identifies the server within the PostgreSQL database. (Read-only) |
205229
| Name | `ACES Main Building` | User-friendly display name for the server. This defaults to the display name of the root of the Geo tree. |
206230
| WebCTRL Version | `8.5.002.20230323-123687` | Full version of the WebCTRL server. (Read-only) |
207-
| Add-On Version | `0.5.10` | Installed version of the PostgreSQL_Connect add-on. (Read-only) |
231+
| Add-On Version | `0.5.12` | Installed version of the PostgreSQL_Connect add-on. (Read-only) |
208232
| IP Address | `123.45.67.89` | External IP address of the server as viewed by the PostgreSQL database. (Read-only) |
209233
| Last Sync | `2024-12-02 14:05:32` | Timestamp of the last successful synchronization. If synced within the last 24 hours, the background color is green; otherwise, the background is red. (Read-only) |
210234
| License | `WebCTRL Premium` | Click this field to download WebCTRL's license. (Read-only) |
@@ -290,7 +314,7 @@ Commands entered into this table are executed on servers during their next sync
290314
| Ordering | `3` | When there are multiple command entries for a single server, this column specifies the ascending order in which commands are executed. |
291315
| Command | `notify "Hello!"` | The command(s) to execute. Multiple commands can be separated by newlines for fail-fast semantics. |
292316

293-
Commands chained together using new-lines in a single entry are fail-fast, which means that execution is terminated immediately when an error is encountered. However, errors in one command entry do not affect other entries. Generally, commands are case-insensitive. Commands are tokenized using whitespace as delimiters. Double quotes can be used if a token must include whitespace. The caret character `^` can be used as an escape character. The local file path are specified, paths starting with `/` or `\` are treated as relative to WebCTRL's installation directory, and paths starting with `./` or `.\` are treated as relative to WebCTRL's active system directory. The two dots in `a/../b` go to the parent folder of `a`, so that `b` would be a sibling folder of `a`. Environment variables enclosed in percent signs (e.g, `%USERNAME%`) are expanded when present in local paths. Single-line comments are supported when a line is starts with `//`. The following commands are supported.
317+
Commands chained together using new-lines in a single entry are fail-fast, which means that execution is terminated immediately when an error is encountered. However, errors in one command entry do not affect other entries. Generally, commands are case-insensitive. Commands are tokenized using whitespace as delimiters. Double quotes can be used if a token must include whitespace. The caret character `^` can be used as an escape character. When local file paths are specified, paths starting with `/` or `\` are treated as relative to WebCTRL's installation directory, and paths starting with `./` or `.\` are treated as relative to WebCTRL's active system directory. The two dots in `a/../b` go to the parent folder of `a`, so that `b` would be a sibling folder of `a`. Environment variables enclosed in percent signs (e.g, `%USERNAME%`) are expanded when present in local paths. Single-line comments are supported when a line is starts with `//`. The following commands are supported.
294318

295319
| Command | Description |
296320
| - | - |
@@ -318,6 +342,8 @@ Commands chained together using new-lines in a single entry are fail-fast, which
318342
| `canApplyUpdate <file_path>` | Asserts that WebCTRL is able to apply the specified *.update* patch file. If the update cannot be applied, then command execution is terminated. |
319343
| `!canApplyUpdate <file_path>` | Asserts that WebCTRL is not able to apply the specified *.update* patch file. If the update can be applied, then command execution is terminated. |
320344
| `updateDST` | Updates daylight savings dates stored in the WebCTRL database and marks controllers for a pending parameter download. |
345+
| `opentunnel <listen_port> <target_port> [timeout]` | Open a reverse SSH tunnel from the WebCTRL server to the SFTP server. `listen_port` is opened on the SFTP server, and connections to this port are forwarded to `target_port` on the WebCTRL server. After `timeout` expires, the tunnel will be closed at the next sync. If `timeout` is undefined, then the tunnel stays open until the next server reboot. |
346+
| `closetunnel [listen_port]` | Close a reverse SSH tunnel that was previously opened with the `opentunnel` command. If `listen_port` is unspecified, all tunnels are closed (excluding those configured in the [SSH Tunnels](#ssh-tunnels) section). |
321347

322348
#### Examples
323349

@@ -349,6 +375,14 @@ log "Update did not install."
349375
rmdir "/update_on_restart"
350376
```
351377

378+
### SSH Tunnels
379+
380+
This webpage permits configuration of persistent reverse SSH tunnels from the WebCTRL server to the SFTP server. The source port is opened on the SFTP server, and any connections to the source port are forwarded through the tunnel to the target port on the WebCTRL server. This can be used to get remote access to the webserver over port 80 or 443, for example. Or it can be used to access RDP with port 3389. Beware that shutting down the WebCTRL service over RDP will terminate the RDP connection because WebCTRL itself is functioning as the VPN tunnel provider. So I would suggest not tunneling RDP in this manner unless all users understand the risks.
381+
382+
![](./resources/ssh_tunnels.png)
383+
384+
Persistent tunnels are checked during each sync. If you reboot the SFTP server and interrupt the tunnel's connection, you will have to wait until the next sync for the tunnel to reform. Therefore, it is suggested to perform maintenance on the SFTP server only when users are unlikely to be utilizing the tunnels.
385+
352386
### Find Trends
353387

354388
This webpage helps you to find the persistent identifier for trends in order to track them in the database. See the next section for more details on how to use this identifier. You can browse the Geographic tree to search for trends. When you find a trend you want to track, click the turquoise colored trend type in order to copy the persistent identifier.
@@ -418,9 +452,13 @@ CREATE INDEX webctrl_trend_data_time ON webctrl.trend_data ("time" DESC);
418452
### Packaged Dependencies
419453

420454
- [PostgreSQL JDBC 42.7.5](https://jdbc.postgresql.org/) - Used to connect to PostgreSQL databases.
421-
- [JSch 0.2.22](https://github.com/mwiede/jsch) - Used to connect to SFTP servers.
455+
- [JSch 0.2.23](https://github.com/mwiede/jsch) - Used to connect to SFTP servers.
422456
- [JSON-java 20250107](https://github.com/stleary/JSON-java) - Used to encode and decode JSON data.
423457

424458
### Server ID Reset
425459

426-
There is a *Reset ID* button on the add-on's main page. This will make the add-on forget its current server ID and re-register with the database at the start of the next sync. This server ID is the primary mechanism for how the database separates out data corresponding to different WebCTRL servers. If you ever need to force the add-on to choose a particular ID, you can craft and submit a particular HTTP request to the WebCTRL server. For example, *https://localhost/PostgreSQL_Connect/index?type=resetNow&newID=3* will force the ID of the WebCTRL server accessible at *localhost* to 3.
460+
There is a *Reset ID* button on the add-on's main page. This will make the add-on forget its current server ID and re-register with the database at the start of the next sync. This server ID is the primary mechanism for how the database separates out data corresponding to different WebCTRL servers. If you ever need to force the add-on to choose a particular ID, you can craft and submit a particular HTTP request to the WebCTRL server. For example, *https://localhost/PostgreSQL_Connect/index?type=resetNow&newID=3* will force the ID of the WebCTRL server accessible at *localhost* to 3.
461+
462+
### Hostname Matching
463+
464+
If a technician copies a WebCTRL system folder over to another computer, then the configuration file for this add-on is also copied. When the other computer starts up, this add-on will try to connect to the PostgreSQL database using the same ID as the original computer. This is a problem. There should not be multiple computers connecting with the same ID. In an attempt to resolve this issue, the add-on stores the hostname of the computer alongside the database ID. When initializing, if the hostname stored in the config file does not match the hostname of the computer, the add-on will become inert. Therefore, if you ever need to change the hostname of a WebCTRL server, you should delete `./programdata/systems/test_system/webapp_data/PostgreSQL_Connect/private/hostname.dat` and restart this add-on.

‎config/BUILD_DETAILS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ webserver-api-9.0.002
3333
webui-9.0.002
3434

3535
Packaged Dependencies:
36-
jsch-0.2.22-sources
37-
jsch-0.2.22
36+
jsch-0.2.23-sources
37+
jsch-0.2.23
3838
json-20250107-sources
3939
json-20250107
4040
postgresql-42.7.5-sources

‎config/EXTERNAL_DEPS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
url:postgresql-42.7.5.jar:https://repo1.maven.org/maven2/org/postgresql/postgresql/42.7.5/postgresql-42.7.5.jar
22
url:postgresql-42.7.5-sources.jar:https://repo1.maven.org/maven2/org/postgresql/postgresql/42.7.5/postgresql-42.7.5-sources.jar
3-
url:jsch-0.2.22.jar:https://repo1.maven.org/maven2/com/github/mwiede/jsch/0.2.22/jsch-0.2.22.jar
4-
url:jsch-0.2.22-sources.jar:https://repo1.maven.org/maven2/com/github/mwiede/jsch/0.2.22/jsch-0.2.22-sources.jar
3+
url:jsch-0.2.23.jar:https://repo1.maven.org/maven2/com/github/mwiede/jsch/0.2.23/jsch-0.2.23.jar
4+
url:jsch-0.2.23-sources.jar:https://repo1.maven.org/maven2/com/github/mwiede/jsch/0.2.23/jsch-0.2.23-sources.jar
55
url:json-20250107.jar:https://repo1.maven.org/maven2/org/json/json/20250107/json-20250107.jar
66
url:json-20250107-sources.jar:https://repo1.maven.org/maven2/org/json/json/20250107/json-20250107-sources.jar

‎resources/postgresql_config.png

915 Bytes
Loading

‎resources/ssh_proxy.png

21.2 KB
Loading

‎resources/ssh_tunnels.png

18.9 KB
Loading

‎root/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<extension version="1">
22
<name>PostgreSQL_Connect</name>
33
<description>Periodically synchronizes operators, add-ons, and specified trends with an external PostgreSQL database.</description>
4-
<version>0.5.10</version>
4+
<version>0.5.12</version>
55
<vendor>Automatic Controls Equipment Systems, Inc.</vendor>
66
<system-menu-provider>aces.webctrl.postgresql.web.SystemMenuEditor</system-menu-provider>
77
</extension>

‎root/webapp/WEB-INF/web.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@
1919
<url-pattern>/index</url-pattern>
2020
</servlet-mapping>
2121

22+
<servlet>
23+
<servlet-name>ProxyPage</servlet-name>
24+
<servlet-class>aces.webctrl.postgresql.web.ProxyPage</servlet-class>
25+
</servlet>
26+
<servlet-mapping>
27+
<servlet-name>ProxyPage</servlet-name>
28+
<url-pattern>/Proxy</url-pattern>
29+
</servlet-mapping>
30+
2231
<servlet>
2332
<servlet-name>TableEditorPage</servlet-name>
2433
<servlet-class>aces.webctrl.postgresql.web.TableEditorPage</servlet-class>

‎src/aces/webctrl/postgresql/core/Command.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,54 @@ public class Command {
5252
}
5353
}
5454
});
55+
commandMap.put("opentunnel", new Cmd(){
56+
@Override public boolean exec(Command c, String[] tokens) throws Throwable {
57+
if (tokens.length==3 || tokens.length==4){
58+
try{
59+
final int listenPort = Integer.parseInt(tokens[1]);
60+
final int targetPort = Integer.parseInt(tokens[2]);
61+
final long timeout = tokens.length==3?0:Long.parseLong(tokens[3]);
62+
if (TunnelSSH.open(listenPort, targetPort, timeout)){
63+
if (Initializer.debug()){
64+
Initializer.log("Tunnel opened from port "+listenPort+" to "+targetPort+(timeout>0?" with a timeout of "+(timeout/60000L)+" minutes.":"."));
65+
}
66+
return true;
67+
}else{
68+
c.sb.append("\n'opentunnel' encountered an error.");
69+
}
70+
}catch(NumberFormatException t){
71+
c.sb.append("\n'opentunnel' failed to parse number from expected value.");
72+
}
73+
}else{
74+
c.sb.append("\n'opentunnel' accepts either 2 or 3 arguments.");
75+
}
76+
return false;
77+
}
78+
});
79+
commandMap.put("closetunnel", new Cmd(){
80+
@Override public boolean exec(Command c, String[] tokens) throws Throwable {
81+
if (tokens.length==1){
82+
TunnelSSH.closeTransient();
83+
if (Initializer.debug()){
84+
Initializer.log("All transient tunnels closed.");
85+
}
86+
return true;
87+
}else if (tokens.length==2){
88+
try{
89+
TunnelSSH.closeTunnel(Integer.parseInt(tokens[1]));
90+
if (Initializer.debug()){
91+
Initializer.log("Tunnel on port "+tokens[1]+" has been closed.");
92+
}
93+
return true;
94+
}catch(NumberFormatException e){
95+
c.sb.append("\n'closetunnel' failed to parse number from expected value.");
96+
}
97+
}else{
98+
c.sb.append("\n'closetunnel' accepts at most one argument.");
99+
}
100+
return false;
101+
}
102+
});
55103
commandMap.put("decrypt", new Cmd(){
56104
@Override public boolean exec(Command c, String[] tokens) throws Throwable {
57105
if (tokens.length==2){
@@ -314,6 +362,7 @@ public class Command {
314362
if (!value.equals(Config.connectionURL)){
315363
Config.connectionURL = value;
316364
Sync.licenseSynced = false;
365+
TunnelSSH.close();
317366
Initializer.status = "Initialized";
318367
c.saveConfig = true;
319368
}

0 commit comments

Comments
 (0)
Please sign in to comment.