Git and Bash Based Encrypted Remote Interactive Shell (GiBBERISh). For when you cannot use SSH.
GPL-3.0 License
![Generic badge](https://img.shields.io/badge/TL;DR-Free and secure remote access with NAT & firewall traversal, using Git as backend-blue.svg)
![Generic badge](https://img.shields.io/badge/Dependency-Git, Bash, cURL-blue.svg) ![Generic badge](https://img.shields.io/badge/Tested on-Ubuntu 12.04, 16.04, 20.04 and Debian 10-blue.svg)
![Maintaner](https://img.shields.io/badge/maintainer-Somajit Dey-blue)
GiBBERISh stands for Git and Bash Based Encrypted Remote Interactive Shell. It is a free, easy and portable solution to securely access your Linux computer from another Linux box over the internet, when none of the machines has a public IP address. You can also transfer files to and fro the remote host, with end-to-end encryption.
GiBBERISh consists of only a short Bash script, to be run on both the hosts (viz. client and server), and an online Git repository owned and controlled by the user, such as a free repository at any of GitHub, GitLab, Bitbucket, SourceForge etc.
The standard way to remote access a Linux computer is Secure Shell (SSH). But, in order to connect to the remote host over internet, it has to have a public IP address -- something most personal and work PCs do not have. You can, however, beat this in the following ways:
If you can afford any of the above, then GiBBERISh is not for you. However, if you are like me, who doesn't want to pay for a VPN or port-forwarding service with static URL, you are probably out of luck with SSH. Also, some corporate firewalls block non-web traffic entirely, hence SSH is not even an option there.
However, you can still manage fine with a freemium remote access app, such as TeamViewer or Anydesk. If you are happily using such Virtual Network Computing (VNC) tools, then too you won't have any use for GiBBERISh. However, VNC apps have their cons. They consume a lot of bandwidth. If you want to access just the shell of your remote host, why would you need to import the entire screen! The remote computer also need to be online all the time. Another common problem is version incompatibility. For TeamViewer, for example, every machine needs to have the same version of the app installed on it, or else it wont work. In addition, being freemium, what they offer for free is limited and you never know how long they would keep offering those free services. To top it all of, you can do nothing but trust these service providers when they say that using their services is completely safe for your systems and your data.
Anyways, I needed a light-weight, portable, easy-to-install and completely free yet reliable platform to access my remote machine securely over the internet. I didn't mind the latency, as long as my main purpose of submitting jobs to the remote host was served. Hence, my DIY solution - GiBBERISh.
An interactive shell is nothing but the user chatting with the operating system (OS), occasionally poking the OS with interrupts or job-control signals. For GiBBERISh, the user's local machine is the client, and the OS in the remote host is the server. Correspondence between the two is managed by an automated Git workflow.
Git, if you don't know about it already, is a widely-used, fast, lightweight, distributed version-control system or content-tracker. Because it is distributed, widely available, and easy to use, you can use it to synchronize two machines quite easily. If you push commits (i.e. make some changes) to a Git repository (i.e. a directory with history) in one machine, you can synchronize or update its clone in another machine simply by fetching those new commits from the former repository (called upstream in Git-speak).
Git can connect two machines over the internet for push/fetch only if at least one of them has a public IP address or URL. However, one can have a free, if not private, online Git repository with a public URL hosted in any of the popular Git-servers, such as GitHub, GitLab, Bitbucket or SourceForge. Although anyone can view (and fetch) the contents of your online repository, you remain in control of who can make changes to your repository. This is because all push access is protected by your password or an access-token generated by you.
Whenever the user enters a command at client, the GiBBERISh script encrypts it with your Git credentials using a state-of-the-art, yet free, cryptography service called GNU Privacy Guard (GPG). It then commits this encrypted text to the user's local repository and pushes the same to the user's upstream repository (say, at GitHub). Server's script then fetches this commit from upstream, decrypts and pipes the user's command to an interactive Bash. The output emitted by Bash is again encrypted, committed and pushed to GitHub, which the client fetches, decrypts and shows to the user. ( For an original implementation of a similar Git-based chat, see https://github.com/ephigabay/GIC ).
Note: Because only encrypted text goes to GitHub, the public cannot see what commands the user is running, or their outputs.
Because of the dependence on an online Git repository, the time between entering a command and getting its output back is not insignificant. This is because git-push
is slow. From multiple measurements with upstream at GitHub, I found the latency varies between 6 to 9 seconds. However, using GitHub's REST API, the git-push
can be avoided and performance improved. When using the API, the latency decreases to 3-4 seconds.
Enter
key.flock
) maybe run easily on Git-Bash from the Git for Windows package. However, that might be unnecessary, given that Windows 10 now ships with a subsystem for Linux (WSL).First, create a dedicated repository at any free Git hosting service such as https://github.com/, https://gitlab.com/, https://bitbucket.org/ or https://sourceforge.net/. I recommend using GitHub, as GiBBERISh can speed itself up using their content API. The repository can be completely empty, i.e. without any commits.
Tip: Also generate a personal access-token (PAT) with write-access to your repository. This makes GiBBERISh even more secure.
Tip: If you have created the repository at GitHub, then install jq
and base64
to experience the lowest latency (3-4s) with GiBBERISh. However, you can also run GiBBERISh without these, albeit with double latency.
At the Linux machine that you want to use as server, run the installer script (no superuser or admin privilege required):
./installer
Then, simply follow the on-screen instructions.
To start the server, run:
gibberish-server
At the client machine, install GiBBERISh similarly as above.
To access the remote server, simply run:
gibberish
Note: GiBBERISh allows, possibly unnecessarily, upstream repositories that are on your local disk or NFS.
GiBBERISh recognizes a few keywords as listed below.
ping | hello | hey | hi
To test if the server is still connected. Consider the following situation. You are running a foreground process on the server, which outputs infrequently. Because it is in foreground, you do not have the command prompt and hence cannot execute a short command such as echo
to see if the server is still responding. Just enter any of these keywords, and the server will send you a 'hello' if it can hear you, without interrupting that foreground process in any way. However, you can also do a simple Ctrl-<spacebar>
or Ctrl-z
to get back the prompt, at the expense of stopping the foreground process. Note: You can use any of these commands anytime, even if a foreground process is running at the server and you do not have a prompt.
exit | quit | logout | hup | bye
To end the session, and disconnect or hangup. When you do this, the current interactive shell in the server is closed and a new, fresh shell is launched ready for the next session. You therefore, would lose any environment variable you had set or function definitions you had sourced during the last session. Beware that this should also close any process running on the server that the exiting shell sends SIGHUP to, unless the process has a handler installed for HUP. Start processes in background with nohup
if you intend to keep them running even after you logout using these keywords.
brb
Be right back, viz. to quickly switch to your local environment for a short while, without ending or interrupting the remote session. Any foreground process running in the server, keeps running uninterrupted. With this keyword, you simply get back your local command prompt, whenever you need to run commands locally during a remote session. To return to the remote session, just enter gibberish
again. You will be shown all the server output since the time you did brb
, so you miss nothing.
local
Run commands locally, i.e. at the client, inside a sub-shell. The syntax is:
local <commands>
# To run codes from a file
local . <path to script>
# To see current working directory at client
local # equivalent to: local pwd
rc
Run commands. This is akin to the .
or source
built-in of Bash. Whereas source
reads commands from a local file and executes them in the current shell, rc
takes commands from a client-side file and executes them in the server-side shell that the user is currently interacting with. The syntax is:
rc <local path to script>
All the commands in the given script are passed to upstream in one Git-push. Hence, use of this keyword helps with latency issues.
take | push
See next section
bring | pull
See next section
help | tutorial
Gives the link to this section
latency |rtt
Gives the latency or round-trip-time for the current connection in seconds. Note: You can use any of these commands anytime, even if a foreground process is running at the server and you do not have a prompt.
Tip: If you need to run a command that matches any of the keywords described above, use the command
built-in of Bash.
Copying file from client to server:
take <local path> <remote path>
#### or
push <local path> <remote path>
Copying file from server to client:
bring <remote path> <local path>
#### or
pull <remote path> <local path>
File transfer is atomic, which guarantees you never end up with a corrupt file, even if the transfer operation gets interrupted or terminated prematurely. If the destination file is existing, it will be overwritten after backup. If the destination path is a directory, the file would be put inside it. The paths can be absolute or relative. As should be intuitive, any relative path would be interpreted with PWD at the corresponding host as its base, i.e. relative local (remote) path would be relative to the client-side (server-side) working directory. Similarly, tilde and shell-variable expansion in the path specifications, are done with respect to the corresponding host.
Tip: To transfer directories or a collection of files, archive them first, with tar
for example, and then use the above commands to exchange that single archive file.
Tip: To transfer a file from the Windows filesystem in WSL, first change its path to Unix path using the command: wslpath
.
Note: File transfer is end-to-end encrypted with your Git credentials. To keep your Git repository size small, the files are transferred using free, public file-hosting servers.
All the familiar Ctrl key generated signals are supported except Ctrl-\
, which has been replaced by Ctrl-e
(mnemonic: 'e' for exit). Because the user doesn't have the liberty to open a second terminal to control a runaway foreground process that ignores SIGTSTP (as generated with Ctrl-z
), GiBBERISh provides Ctrl-<spacebar>
key-binding to force pause a foreground process with SIGSTOP - which cannot be ignored or handled.
If you are a first-time user, you may want to get familiar with GiBBERISh at your local machine. To do that, install GiBBERISh twice, once as server and then as client. Now, open a terminal and run:
export GIBBERISH=server
gibberish-server
Then, open another terminal and run:
export GIBBERISH=client
gibberish
There might be desperate situations where you need to grant access to your local machine to someone else. To do that safely, do the following.
~/.gibberish
directory before reinstallation. After the session, she can simply restore the backup.]gibberish-server <access-token>
gibberish
Ctrl-c
, Ctrl-z
or Ctrl-\
as usual. In the worst case, simply close the terminal. It would kill all processes spawned in that session.The Bash script for GiBBERISh, viz. gibberish_rc.bash, is a library rather than an executable. On startup, your interactive Bash sources this script. The installer puts the corresponding run-command in .bashrc.
The installer, on the other hand, is an executable script. Hence, do a chmod +x on it, if necessary.
The client-side and server-side codes are almost the same structurally. This is why a single shell-script suffices for both. There are two types of events:
Internal events: User@client or Bash@server creates new data to push
External events: Git-fetch brings new data from outside to display or process
Internal events are mostly waited for, while external ones need to be polled for continuously. Everything else is mostly triggered by these events. For example, upon creation, the data to be pushed is first siloed and an encrypt-commit-push pipeline is triggered.
The Git repository for GiBBERISh has two linear branches, viz. server-branch and client-branch. Server fetches from server-branch, but pushes to client-branch. Client does the converse. Every user-command@client or Bash-output@server, is committed to the proper outgoing branch as a single text file. As new commands or outputs become available, this file is simply revised to hold the same. The Git-worktree, therefore, always contains only a single file, and all the commands/outputs can always be tracked from its revision history. Every time a commit happens, it triggers a post-commit hook that pushes the commit automatically.
Every iteration of Git-fetch triggers a checkout sequence that tracks all the new commits chronologically, restoring the corresponding version of the abovementioned text file for decryption. Upon successful decryption, the checkout function pushes the data down a pipeline to user (@client) or Bash (@server), before moving on to the next commit in the git-revision-list.
Note that if every user-command was passed using that single text file alone, then all the commands would end up in the queue for Bash@server. To relay a user-generated signal (SIGINT, SIGTSTP, SIGQUIT) to a foreground process on demand, however, we need to route kill
commands to a second, parallel shell in the server. We, therefore, need a way to commit commands in the repository other than the single text file mentioned before. GiBBERISh exploits Git's commit-message field for this purpose. Only those commits that carry the user-generated control signals, or any other command that is to be executed by the parallel shell only, would have commit-messages which hold the commands themselves. All other commits have empty commit-messages. Whether the commit is meant for the main shell or the parallel, can therefore be ascertained simply by checking whether the commit-message is empty or not.
Such commit-message commands, meant for the parallel shell, are mostly kill(@server) and file-download(@client) commands. Hence, they do not contain any sensitive data and are consequently not encrypted to save on time. These commands are called hooks. Both server and client share this hook-execution architecture. Hooks are the only way server can make client do some work, such as required during file transfer from server to client.
In contrast to SSH, GiBBERISh does not stream every keystroke made by the user to the server in real-time. Rather, it first lets the user enter the complete command, then reads it, checks for keywords, and only then decides what to do. If the command-line is not a keyword, it is pushed to the server as is for execution. To generate signals at server from particular key-sequences entered at the client, key-binding is done at the client-terminal such that it commits and pushes the appropriate hook to be executed by the parallel shell at server, whenever those keys are pressed by the user.
The current prompt at the server-side terminal is appended to the text file in the repository below the PGP message block containing the output of Bash@server. When a foreground process is running in that terminal, only a newline is appended instead. Client extracts this appendix to remain informed about the current state of the dynamic server-side prompt, and displays the same at the client-side terminal as required. This feature is strictly designed for adding to the user experience and is in no way a necessary part of the GiBBERISh engine.
git-push
is the most obvious way to transfer a file to upstream. However, it is slow (~3s). Using a file-update API, if provided by the upstream host, can make file-transfer faster (~1s). GiBBERISh uses the REST API of GitHub for this purpose. The speed up is significant:
Using API: 3-4s | Using git-push: 6-9s
Create issue(s) and comment at https://github.com/SomajitDey/gibberish
You can also contact me directly at [email protected]
GiBBERISh-- Git and Bash Based Encrypted Remote Interactive Shell
Copyright (C) 2021 Somajit Dey, ORCID ID: 0000-0002-6102-9777
You are free to modify and distribute the code under GPL-3.0-or-later https://www.gnu.org/licenses/
GiBBERISh comes with ABSOLUTELY NO WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Use it at your own risk. The developer or copyright holder won't be liable for any damage done by or done using this software.
Soumalya Bhowmik contributed by testing this software thoroughly.
Thanks to Patrick Bedat and Seth Kenlon for encouragement and discussions.