Ubicloud has supported both a web and API interface since launch. Recently, we decided to also support a command line interface, so that users can interact with Ubicloud directly from their terminal. To implement the command line interface, we took inspiration from old computer terminals. Everything old is new again.
Traditionally, command line interface programs that interact with web services work like this. The program parses the arguments given by the user, determines the API endpoints, and then submits requests to those endpoints. The program then takes the responses from those endpoints and formats them for display to the user.
We’ll be using the following terms in this post:
The traditional approach has two main problems. First, every new feature added to the client requires that users update their client in order to use the feature. This gives up the main advantage of web distribution, which is that users can immediately benefit from new features and bug fixes as soon as you put them into production. Second, it is harder to detect bugs in the command line interface, since bugs will generally occur in the client, on the user's machine, and you have to hope the user reports them.
Let's see how we mitigate these issues with the Thin CLIent approach.
With the Thin CLIent approach, the client does no parsing of the arguments given by the user. Instead, it just sends all arguments to a single endpoint, which we’ll call the gateway. The gateway endpoint handles requests and responses in the following way:
Using the Thin CLIent approach offers the following advantages:
The Thin CLIent approach has some disadvantages compared to the traditional approach:
We believe the advantages of the Thin CLIent approach heavily outweigh the disadvantages.
When using a Thin CLIent approach for a client that executes other programs, you have to take additional care in regards to security. For example, if the gateway endpoint is compromised and instructs the client to run rm -rf /, you do not want the client to execute that. To mitigate the damage a rogue gateway endpoint can inflict on a client, you need to limit the programs the client is allowed to execute, and limit the arguments to those programs.
Ubicloud's client (named ubi), can only execute the following programs:
ubi will refuse to execute a program that is not one of these 6 programs. Additionally, ubi requires that the program name be one of the arguments it was passed (preventing the server from requesting execution of a different program).
Additionally, ubi enforces the following restrictions on the arguments:
These restrictions are sufficient to allow the 6 commands above to work safely. It's possible we will need to adjust these restrictions if we support additional programs in the future. The goal will remain to limit the flexibility when executing programs to the minimum needed for the client to function.
Using command line options or arguments for configuration of the client is not possible with the Thin CLIent approach, since the client does not parse them, it only sends them to the gateway endpoint. So you cannot use an approach similar to ubi --gateway https://api.example.com/cli … to specify a custom endpoint. For ubi , we decided to have all program configuration be through environment variables. It would be possible to have the client look for configuration in a file at a known location, but the only required configuration option is the token used for the request (which should be secret), and security-conscious organizations are probably not going to want to store secrets in files on local machines.
There are many different systems that handle secret storage, and it's infeasible for ubi to attempt to integrate with all of them. So instead, ubi only deals with environment variables, and users can use whatever secret storage approach they want, as long as they call ubi with the appropriate environment variables set.
As Ubicloud is written mostly in Ruby, it should come as no surprise that ubi was prototyped in Ruby. However, we do not want to require that our users have Ruby installed in order to use ubi. From our initial planning stages, our goal was to ship dependency-free native clients for popular operating systems, so that users could run `ubi` without installing anything else.
We ultimately decided to port the Ruby version to Go. While not as programmer friendly as Ruby, Go is still relatively easy to work with, our engineering team has experience with it, and importantly, it allows us to easily ship native clients for macOS, Linux, and Windows.
As our command line interface is new, we are eager to receive feedback regarding it. If you are interested, please review the documentation (https://www.ubicloud.com/docs/quick-start/cli) and try it out. You can then provide feedback at support@ubicloud.com or by opening a discussion on GitHub.