thesis/content/a_tour_of_tfw.tex

481 lines
26 KiB
TeX
Raw Normal View History

2018-12-01 23:43:34 +00:00
\chapter{A Tour of TFW}\label{atouroftfw}
The bulk of the functionality provided by the Tutorial Framework
is implemented in pre-made components that are built upon the architecture
described in Chapter~\ref{architecture}.
To create tutorials, developers generally rely on and use TFW provided components to
implement most of the common functionality and then build more exercise-specific logic
based on the TFW architecture themselves.
A \emph{very important} point to keep in mind is that most of this
exercise-specific logic will be implemented in \textbf{FSM callbacks} and
custom \textbf{event handlers}.
The whole framework is built in a way to facilitate this process and developers
who understand this mindset almost always find it a breeze to create great
content using TFW\@.
2018-12-01 15:39:05 +00:00
The purpose of this chapter is to further detail the built-in components
provided by the framework.
As previously mentioned before, these components are implemented as event handlers
running in the \code{solvable} Docker container which communicate with frontend
2018-12-01 15:39:05 +00:00
components written in Angular.
Some components might only feature one of these however,
for instance the built-in code editor requires a frontend component and an event
handler to function properly, while the component responsible for
drawing out and managing frontend components implements no
event handler, so it purely exists in Angular.
Another example of a purely frontend component would be the messages component,
which is used to display messages to the user.
2018-12-01 15:39:05 +00:00
In the Tutorial Framework most of the built-ins define APIs, which are TFW messages
that can be used to interact with them.
For example, to inject a command into the terminal one would use a message like this:
\begin{lstlisting}[captionpos=b,caption={An API message capable of writing to the terminal}]
2018-12-01 15:39:05 +00:00
{
"key": "shell",
"data":
{
"command": "write",
"value": "echo 'Hello TFW World!'\n"
}
}
\end{lstlisting}
Notice the ``\code{\n}'' at the end of the command.
By including a newline character, we are also capable of executing commands directly
in the user's terminal (and the user can see this).
2018-12-01 15:39:05 +00:00
Were this newline omitted, the command would only be written to the terminal
--- but not automatically executed --- for users to inspect and potentially execute themselves.
2018-12-01 15:39:05 +00:00
Some components emit or broadcast messages on specific events, for instance the
\code{FSMManagingEventHandler} broadcasts the following message on state transitions:
\begin{lstlisting}[captionpos=b,caption={An FSM update message}]
2018-12-01 15:39:05 +00:00
{
"key": "fsm_update",
"data" :
{
"current_state": ...string...,
"valid_transitions": ...[array of {"trigger": ...string...} objects]...,
"in_accepted_state": ...boolean...,
"last_event": ...
object like
{
"from_state": ...string...,
"to_state": ...string...,
"trigger": ...string...,
"timestamp": ...UNIX timestamp...
2018-12-01 15:39:05 +00:00
}
...
}
}
\end{lstlisting}
As you can see this message contains loads of useful information regarding what is
exactly happening in the tutorial at a given point in time and can be used by client code
2018-12-01 15:39:05 +00:00
to make informed decisions based on this knowledge.
It is not the purpose of this text to provide a complete API documentation however, so in the
2018-12-01 15:39:05 +00:00
following I am only going to explain possibilities provided by given components rather
than showcasing actual, real-life API messages.
\section{Messages Component}
2018-12-03 15:38:22 +00:00
The framework must allow content creators to communicate with the user,
and provide some mechanism to enable ``talking'' to them.
This is the responsibility of the \emph{messages} component, which
provides a chatbox-like element on the frontend.
The simplest form of communication it accommodates it the insertion of text
into the chatbox through API messages.
2018-12-03 15:38:22 +00:00
This component always expects messages it receives to be in Markdown%
\footnote{\href{https://daringfireball.net/projects/markdown/}
{https://daringfireball.net/projects/markdown/}} format,
so that it is possible to nicely format any text that one might want to display.
This is especially important when displaying inline code with text around it,
so that it is easier to read for the user.
Every message has an optional \emph{originator} field, which serves
to remind the user of the purpose of the given message.
These messages are also timestamped so that it is easier to navigate through them
and look back older messages from the user.
2018-12-03 15:38:22 +00:00
If no timestamp is present in the API message, then it will be added on
the frontend.
This is useful, because this will use system time on the user's machine,
and as such time zones will not be an issue (whereas if we suggested adding
timestamps to the messages on the backend, content creators would have to
deal with conversions between time zones).
\pic[width=.5\textwidth]{figures/chatbot.png}{The avataobot typing in the messages component}
A particularly interesting feature of the messages component is that TFW client code
can queue a bunch of messages for the component to display one by one, separated by
appropriate pauses in time so that the user is capable of conveniently reading through all
off them.
Similarly to a real chat application, some
``jumping dots'' indicate if the bot is still ``typing'' something.
The timing of pauses and messages is based on the \emph{WPM} --- or Words Per Minute ---
set by developers according to their specific requirements.
This creates an experience similar to chatting with someone in real time, as the time
it takes for each message to be displayed depends on the length of the previous message.
This illusion is made possible through appropriate \code{setTimeout()} calls in
TypeScript and some elementary math to calculate the proper delays in milliseconds based on
message lengths:
\[ charactersPerMinute = wordsPerMinute * 5 \]
\[ charactersPerSeconds = charactersPerMinute / 60 \]
\[ timeoutSeconds = lastMessageLength / charactersPerSeconds \]
\[ timeoutMilliseconds = timeoutSeconds * 1000 \]
The value 5 comes from the fact that on average English words are 5
characters long according to some studies.
2018-12-03 15:38:22 +00:00
This value could be made configurable in the future, but currently there
are no plans to make non-English challenges.
\section{IDE Component}\label{idecomponent}
This is the code editor integrated into the frontend of the framework.
It allows users to select, display and edit files.
Developers can configure which directory of the file system should the editor list files from.
The editor features the ``Deploy'' button referred to earlier in this paper, which is
capable of restarting processes that might be running code from a file visible in the editor.
To implement this IDE%
\footnote{Integrated development environment}
I have integrated the open source Monaco editor developed by Microsoft into the
Angular web application TFW uses as a frontend, and added functionality to it
that allows the editor to integrate with the framework.
This involves communication with an event handler dedicated to this feature,
which is capable of reading and writing files to disk, while sending and receiving
editor content from the frontend component.
The interaction of this event handler and the Monaco editor provides a seamless
editing experience, featuring auto-save at configurable intervals, code completion,
automatic code coloring for several programming languages and more.
Perhaps the most ``magical'' feature of this editor is that if any process
in the Docker container writes a file that is being displayed in the editor,
the contents of that file are automatically refreshed without any user
interaction whatsoever.
Besides that, if a file is created in the directory the editor is configured
to display, that file is automatically displayed on a new tab in the IDE\@.
This allows for really interesting demo opportunities.
Let's say I create a file using the terminal on the frontend by executing the
command \code{touch file.txt}. A new tab on the editor automatically
appears. If I select it I can confirm that I have successfully created an
empty file.
After this let's run a \code{while} cycle in the command line which
periodically appends some text to \code{file.txt}:
\begin{lstlisting}[captionpos=b,caption={Bash while cycle writing to a file periodically},
language=bash]
while true
do
echo 'Hey there!' >> file.txt
sleep 1
done
\end{lstlisting}
The results speak for themselves:
\pic{figures/ide_demo.png}{The editor demo involving automatic file refreshing}
As you can see, the file contents are automatically updated as the bash script appends
to the file.
This feature is implemented by using the inotify API%
\footnote{\href{http://man7.org/linux/man-pages/man7/inotify.7.html}
{http://man7.org/linux/man-pages/man7/inotify.7.html}}
provided by the Linux kernel to monitor file system events involving the directory listed by
the editor. The event handler of the editor hooks callbacks to said events which notify the
Tutorial Framework to reload the list of files in the directory as well as the contents of
the selected file.
The code making this feature possible is reused several times in the framework
for interesting purposes such as monitoring the logs of processes.
2018-12-03 15:38:22 +00:00
It is also worth to mention that integrating such file monitoring into the framework
is not quite as simple as described above, because one has to deal with many issues like the
semi-undeterministic nature of how a single file modification can sometimes result in
several inotify events, or implement rate limiting for the whole thing to avoid
saturating the messaging system with file content updates triggered by said events
being triggered too frequently.
The editor also allows content creators to completely control it using API messages.
This involves the selecting, reading and writing of files as well as changing the
selected directory.
These features allow content creators to ``guide'' a user through code bases
for example, where in each step of a tutorial a file is opened and explained
through messages sent to the chatbox of the messages component.
2018-12-03 15:38:22 +00:00
Developers have to \emph{explicitly} allow directories one by one to be listed by the
editor. This is done to avoid access control issues in case the editor is
running with more permissions than the user should have%
\footnote{Actually this involves extra caution, such as dealing with
2018-12-03 15:47:09 +00:00
symlinks in an allowed directory which could point to other, non-allowed locations.}.
2018-12-03 15:38:22 +00:00
It is also possible to blacklist file patterns (so that binary files can be
excluded for example, as a text editor is not suitable to deal with these).
\section{Terminal Component}
This is a full-fledged xterm terminal emulator running right in the user's browser.
I have partially derived this from my early work I have briefly mentioned
in~\ref{intro:emergence}, but it required lot's of new functionality to work the way
it does today.
The difference this time is that I had to integrate it into an Angular component.
I have used the awesome xterm.js%
\footnote{\href{https://xtermjs.org}{https://xtermjs.org}}
terminal emulator to do so.
This component has a tiny server process which is managed by a TFW event handler.
This small server is responsible for spawning bash sessions and
UNIX pseudoterminals (or \code{pty}s) in the \code{solvable} Docker
container.
It is also responsible for connecting the master end of the \code{pty} to the
2018-12-03 15:38:22 +00:00
emulator running in the browser and the slave end to the bash session it has
spawned.
This way users are able to work in the shell displayed on the frontend just like
they would on their home machines, which allows for great tutorials
explaining topics that involve the usage of a shell.
Note that this allows us to cover an extremely wide variety of topics using TFW\@:
from compiling shared libraries for development, to using cryptographic FUSE file systems
for enhanced privacy%
\footnote{\href{https://github.com/rfjakob/gocryptfs}{https://github.com/rfjakob/gocryptfs}},
2018-12-03 15:38:22 +00:00
or automating cloud infrastructure using Ansible%
\footnote{\href{https://www.ansible.com}{https://www.ansible.com}}.
2018-12-03 15:38:22 +00:00
This component exposes several functions through TFW API messages, such as injecting commands
to the terminal, reading command history and registering callbacks that are invoked when
2018-12-03 15:38:22 +00:00
the user executes anything in the terminal.
This allows content developers to implement functionality such as advancing the
tutorial when a certain command was invoked, or detect common mistakes in using certain
tools, such as warning users when they try to use an outdated cipher when
encrypting a file using \code{openssl}, or if they generate an RSA key with a small key size.
2018-12-03 15:38:22 +00:00
\pic{figures/terminal.png}{The frontend terminal of TFW running top}
2018-12-01 23:43:34 +00:00
The implementation of reading command history is quite an exotic one.
2018-12-02 15:02:56 +00:00
The framework needs to be able to detect if the user has executed any command in the
container using an interactive bash session.
This is not an easy thing to accomplish without relying on some sort of heavyweight
2018-12-01 23:43:34 +00:00
monitoring solution such as Sysdig%
\footnote{\href{https://sysdig.comq}{https://sysdig.com}}.
I deemed most similar systems a huge overkill to implement this functionality, and their
2018-12-03 15:38:22 +00:00
memory footprints are not something we could afford here%
\footnote{These containers will be spawned on a per-user basis, so we must be as
2018-12-03 15:47:09 +00:00
conservative with memory as possible.}.
Another way would be to use \code{pam_tty_audit.so} in the PAM%
2018-12-01 23:43:34 +00:00
\footnote{Linux Pluggable Authentication Modules:
\href{http://man7.org/linux/man-pages/man3/pam.3.html}
{http://man7.org/linux/man-pages/man3/pam.3.html}}
2018-12-02 15:02:56 +00:00
configurations responsible for logins, as this allows for various TTY auditing functions,
2018-12-03 15:38:22 +00:00
but I have found an even simpler approach to solving this problem in the end.
It is possible to set up the user's environment in
such a way during the build of the image, that I can enforce and determine the
location of the bash \code{HISTFILE}%
2018-12-01 23:43:34 +00:00
\footnote{This environment variable contains the path to the file bash writes command
2018-12-03 15:47:09 +00:00
history to.}
2018-12-01 23:43:34 +00:00
of the user.
2018-12-03 15:38:22 +00:00
By combining this with the inotify system built into TFW,
the framework can monitor changes made to this file and read the commands executed
2018-12-01 23:43:34 +00:00
by the user from it.
2018-12-02 15:02:56 +00:00
It is important to keep in mind that the user is able to ``sabotage'' this method%
2018-12-03 15:47:09 +00:00
\footnote{By unsetting the \code{HISTFILE} envvar for example.},
2018-12-02 15:02:56 +00:00
but that should not be an issue as this is not a feature that is intended to be
used in competitive environments (and if the users of a tutorial intentionally
break the system under themselves, well, good for them).
2018-12-01 23:43:34 +00:00
Another advantage of this method is that this can be applied to any interactive
application that supports logging commands executed in them in some way or another.
2018-12-03 15:38:22 +00:00
A good example would be GDB%
\footnote{\href{https://www.gnu.org/software/gdb/}{https://www.gnu.org/software/gdb/}},
which supports an option called \code{set trace-commands on}. This option flushes
command history to a file after every executed command.
This feature can be combined with the file monitoring capabilities of the framework, and now
we can even detect commands executed inside GDB by the user.
This is a good example of the flexibility provided by this solution. Feature requests
like ``I'd like to create a tutorial about <insert software here>'' are quite common, and
supporting them is really easy using this extensible system.
\section{Console Component}
This component is a simple textbox that can be used to display anything to the user,
from the results of unit test to the output of processes.
The console has no event handler: it is a purely frontend component which exposes a simple
API through TFW messages to write and read it's contents.
It works great when combined with the process management capabilities of the framework:
if configured to do so it can display the output of processes like web servers in real time.
2018-12-03 15:38:22 +00:00
When using this next to the TFW frontend editor, it allows for a development
experience similar to working in an IDE on your laptop.
2018-12-03 15:38:22 +00:00
Similarly to other developer tools, I chose to display this component inside the terminal
window, so that the user can switch between the two in order to conserve space using tabs.
It is also possible to configure which one should be displayed by default,
as well as switching between them mid-tutorial using API messages.
\pic{figures/console_and_editor.png}{The console displaying live process logs next to the TFW editor}
2018-12-02 15:02:56 +00:00
\section{Process Management}\label{processmanagement}
The framework includes an event handler capable of managing processes running inside
the \code{solvable} Docker container.
The capabilities of this component include the starting, stopping and restarting of processes,
2018-12-03 15:38:22 +00:00
as well as emitting the standard out or standard error logs belonging to them, even
in real-time (by broadcasting TFW messages).
This logging feature allows for interesting possibilities such as the handling
2018-12-03 15:38:22 +00:00
of live process output, or just requesting the logs belonging to a certain application when
some sort of event has occurred (such as on errors).
This component also can be interacted with using TFW API messages.
The ``Deploy'' button on the code editor uses this component to restart
2018-12-03 15:38:22 +00:00
processes, and the console component also uses this event handler to display
real-time logs.
The Tutorial Framework uses supervisor%
\footnote{\href{http://supervisord.org}{http://supervisord.org}}
to run multiple processes inside a Docker container
(whereas usually Docker containers run a single process only).
2018-12-03 15:38:22 +00:00
This is going to be explained further in Chapter~\ref{usingtfw}.
It is also possible to find out what files does a process write logs to.
Combining this with the inotify capabilities of TFW explained
briefly in~\ref{idecomponent}, it becomes possible to implement live log monitoring
in the framework.
The features involving the use of inotify were among the most difficult ones implement,
2018-12-03 15:38:22 +00:00
since the sheer number of almost impossible to debug issues that such
a complex system could come with.
2018-12-03 15:38:22 +00:00
I'll briefly explain such a bug, which I've found to be immensely exciting.
To understand this, it is necessary to signify, that the inotify API supplied by
the Linux kernel is not capable
of monitoring a single file, it is only able to watch whole directories.
I was unaware of this fact before running into this issue, as the Python
bindings I was using did not warn me about supplying a filename on top of
the directory path, and just stripped down the filename silently%
\footnote{In software development it is considered a bad practice to do such things
implicitly. It is better to fail loud and clear instead of trying to figure out
what the user meant to do.}.
During the initial development of this feature all processes inside the
\code{solvable} Docker container were writing their logs to files
in the FHS%
\footnote{The File Hierarchy Standard is a standard maintained by the Linux foundation,
defining a common directory structure for compliant systems. See
\href{https://wiki.linuxfoundation.org/lsb/fhs}{https://wiki.linuxfoundation.org/lsb/fhs}}
location \code{/tmp/}.
All logs coming from the container itself were also logged to this location.
This had caused an infinite recursion: when a process would write to \code{/tmp/}
inotify would invoke a process that would also log to that location causing the kernel to
emit more inotify events, which in turn would cause more and more new processes to spawn
and write to \code{/tmp/}, causing the whole procedure to repeat again and again.
2018-12-03 15:38:22 +00:00
This continued until my machine would start to run out of memory and begin swapping
pages to disk%
\footnote{When a modern operating system runs out of physical RAM, it is going to swap
2018-12-03 15:47:09 +00:00
virtual memory pages to disk so it can continue to operate --- slowly.}
like crazy, causing the whole system to spiral downwards
in a spectacular fashion until the whole thing managed to crash.
2018-12-03 15:38:22 +00:00
It was an event of such rare and chaotic beauty, that I often fondly recall it to this day.
After my first encounter with the bug I decided to have lunch instead.
Of course it would take me several hours to identify the exact causes behind this
2018-12-03 15:38:22 +00:00
fascinating phenomenon, but those were \emph{very} fun hours at least.
2018-12-01 23:43:34 +00:00
\section{FSM Management}
I have already mentioned the event handler called \code{FSMManagingEventHandler},
which is responsible for managing the framework FSM\@.
2018-12-03 15:38:22 +00:00
For completeness I chose to include it in this chapter as well.
2018-12-01 23:43:34 +00:00
The API it exposes through TFW messages allows client code to attempt stepping the
state machine.
As previously explained this is something that is considered to be a \emph{privileged}
operation in case authentication is enabled, causing the event handler to accept
digitally signed messages only.
In the event of a successful FSM step, this component is going to broadcast
a message showcased as an example in the beginning of Chapter~\ref{atouroftfw}.
2018-12-02 15:02:56 +00:00
\section{Web Component}
The web component allows developers to configure what web application should
be displayed on the frontend.
There are two options for doing this:
\begin{enumerate}
\item Supply the URL of a web application served from the
Docker container to be displayed
\item Implement an Angular component on the frontend and display it
\end{enumerate}
In most cases, when developers make tutorials on Python and Java topics for
example, they will implement a web application in the respective programming
language the exercise is about and most developers have no experience in
Angular development.
This is why more often then not the fist option is chosen.
This feature is implemented by embedding a standard HTML iframe%
\footnote{\href{https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe}
{https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe}}
inside the frontend, which is controllable by TFW
(as an example it can be reloaded or navigated from the framework).
Watchful readers might now be thinking about how does ``controlled by the framework''
thing work with the Same Origin Policy%
\footnote{\href{https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy}
{https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin\_policy}}
being in effect?
The answer is that developers must use a \emph{relative URL}, that is an URL relative
2018-12-03 15:38:22 +00:00
to the entry point of the TFW frontend itself.
2018-12-02 15:02:56 +00:00
To allow serving several web applications from a single port the framework
supports optional reverse-proxy configurations through the nginx%
\footnote{\href{http://nginx.org}{http://nginx.org}} web server ran by the framework.
2018-12-03 15:38:22 +00:00
More on this in a Chapter~\ref{usingtfw}.
2018-12-02 15:02:56 +00:00
\section{Various Frontend Features}
2018-12-03 15:38:22 +00:00
The Angular frontend of the framework features several different layouts.
These layouts are useful to accommodate different workflows for users,
such as the previous example of editing code and being able to view the
2018-12-02 15:02:56 +00:00
result of said code in real time next to the editor.
Another example would be editing Ansible playbooks in the file editor,
and then trying to run them in the terminal.
There are also almost full screen views for each component that makes sense
2018-12-03 15:38:22 +00:00
to be used like that, for instance the code editor can be used to conveniently
edit larger files this way.
2018-12-02 15:02:56 +00:00
2018-12-03 15:38:22 +00:00
The frontend was designed in a way to be fully responsive in window sizes
that still keep the whole thing usable (i.e.\ it would not be practical to start
2018-12-03 15:38:22 +00:00
solving TFW tutorials on a smart phone, simply because of size limits, so such small screens are
2018-12-02 15:02:56 +00:00
not supported, but the frontend still behaves as expected on small laptops or bigger tablets).
This is not an easy thing to implement and maintain due to the lots of small
incompatibilities between browsers given the complexity of the frontend.
2018-12-02 15:02:56 +00:00
Just remember that a few years ago the clearfix%
\footnote{\href{https://stackoverflow.com/questions/8554043/what-is-a-clearfix}
{https://stackoverflow.com/questions/8554043/what-is-a-clearfix}}
hack was the industry standard in creating CSS layouts.
The situation has improved \emph{a lot} since then with flexboxes
and grid layouts despite the sheer chaos that is generally involved in web
standardization efforts, but CSS especially%
2018-12-02 15:02:56 +00:00
\footnote{\href{https://developer.mozilla.org/en-US/docs/Web/CSS/CSS3}
{https://developer.mozilla.org/en-US/docs/Web/CSS/CSS3}}.
The framework frontend is built on grid layout and flexboxes%
\footnote{\href{https://developer.mozilla.org/en-US/docs/Web/CSS/CSS\_Grid\_Layout}
{https://developer.mozilla.org/en-US/docs/Web/CSS/CSS\_Grid\_Layout}},
which gives us the best hopes of being able to maintain it down the line.
It would involve unimaginable horrors to support this multi-layout
2018-12-03 15:38:22 +00:00
frontend on older browsers without flexboxes and grid, so I have
decided to avoid spending development time on these and make sure that
it works like a charm in reasonably modern browsers.
2018-12-02 15:02:56 +00:00
Arguably this is a good thing, as people should keep their browsers up to date to
follow frequent security patches anyway, so let this serve as a reminder to
developers looking to get into IT security that the first step is to
keep your software up to date.
2018-12-03 15:38:22 +00:00
\pic{figures/tfw_grid.png}{The grid layout of the TFW frontend showcased from developer tools}
There are several additional APIs exposed by the frontend,
which include the changing of layouts, selecting the terminal or console
2018-12-02 15:02:56 +00:00
component to be displayed, the possibility of dynamically modifying
frontend configuration values (such as the frequency of auto-saving the files in the editor)
2018-12-01 23:43:34 +00:00
and more.
2018-12-03 15:38:22 +00:00
To accommodate communication with the TFW server, the frontend of the framework
2018-12-03 15:38:22 +00:00
comes with some library code which can be used to send and receive TFW messages.
This code is mostly WebSockets combined with RxJS%
\footnote{\href{https://rxjs-dev.firebaseapp.com}{https://rxjs-dev.firebaseapp.com}},
which makes it easier to write completely asynchronous, callback based code.
The observables%
\footnote{\href{http://reactivex.io/documentation/observable.html}
{http://reactivex.io/documentation/observable.html}}
provided by RxJS are used all around the TFW frontend, as our library code
exposes the operation of receiving data from WebSockets as observables.
Client code can subscribe to these observables using callbacks,
which will be invoked when the observable emits a new value
(i.e.\ when a new message was received on the WebSocket).
When using Angular it is generally a good idea to get familiar with reactive programming,
because it is very easy to get lost in the callback hell without it.