\chapter{Framework Architecture} \section{Core Technology} It is important to understand that the Tutorial Framework is currently implemented as two Docker images: \begin{itemize} \item the \texttt{solvable} image is responsible for running the framework and the client code depending on it \item the \texttt{controller} image is responsible for solution checking (to figure out whether the user completed the tutorial or not) \end{itemize} During most of this capter I am going to be discussing the \texttt{solvable} Docker image, with the exception of section~\ref{solutioncheck}, where I will dive into how the \texttt{controller} image is implemented. The most important feature of the framework is it's messaging system. Basically what we need is a system where processes running inside a Docker container would be allowed to communicate with eachother. This is easy with lots of possible solutions (named pipes, sockets or shared memory to name a few). The hard part is that frontend components running inside a web browser --- which could be potentially on the other side of the planet --- would also need to partake in said communication. So what we need to create is something of a hybrid between an IPC system and something that can communicate with JavaScript running in a browser connected to it. The solution the framework uses is a proxy server, which connects to frontend components on one side and handles interprocess communication on the other side. This way the server is capable of proxying messages between the two sides, enabling communitaion between them. Notice that this way what we have is essentially an IPC system in which a web application can ``act like'' it was running on the backend in a sense: it is easily able to communicate with processes on the backend, while in reality the web application runs in the browser of the user, on a completely different machine. \begin{note} The core idea and initial implementation of this server comes from Bálint Bokros, which was later redesigned and fully rewritten by me to allow for greater flexibility (such as connecting to more than a single browser at a time, different messaging modes, message authentication, restoration of frontend state, a complete overhaul of the state tracking system and the possibility for solution checking among other things). If you are explicitly interested in the differences between the original POC implementation (which is out of scope for this thesis due to lenght constraints) and the current framework please consult Bálint's excellent paper and Bachelor's Thesis on it\cite{BokaThesis}. \end{note} Now let us take a closer look: \subsection{Connecting to the Frontend} The old way of creating dynamic webpages was AJAX polling, which is basically sending HTTP requests to a server at regular intervals from JavaScript to update the contents of your website (and as such requiring to go over the whole TCP handshake and the HTTP request-response on each update). This has been superseded by WebSockets around 2011, which provide a full-duplex communication channel over TCP between your browser and the server. This is done by initiation a protocol handshake using the \texttt{Connection: Upgrade} HTTP header, which establishes a premanent socket connection between the browser and the server. This allows for communication with lower overhead and latency facilitating efficient real-time applications. The Tutorial Framework uses WebSockets to connect to it's web frontend. The framework proxy server is capable to connecting to an arbirary number of websockets, which allows opening different components in separate browser windows and tabs, or even in different browsers at once (such as opening a terminal in Chrome and an IDE in Firefox). \subsection{Interprocess Communication} To handle communication with processes running inside the container TFW utilizes the asynchronous distributed messaging library ZeroMQ% \footnote{\href{http://zeromq.org}{http://zeromq.org}} or ZMQ as short. The rationale behind this is that unlike other messaging systems such as RabbitMQ% \footnote{\href{https://www.rabbitmq.com}{https://www.rabbitmq.com}} or Redis% \footnote{\href{https://redis.io}{https://redis.io}}, ZMQ does not require a daemon (message broker process) and as such has a much lower memory footprint while still providing various messaging patterns and bindings for almost any widely used programming language. An other --- yet untilized --- capability of this solution is that since ZMQ is capable of using simple TCP sockets, we could even communicate with processes running on remote hosts using the framework. There are various lower level and higher level alternatives for IPC other than ZMQ which were also considered during the desing process of the framework at some point. A few examples of top contenders and reasons for not using them in the end: \begin{itemize} \item The handling of raw TCP sockets would involve lot's of boilerplate logic that already have quality implementations in messaging libraries: i.e.\ making sure that all bytes are sent or received both require checking the return values of the libc \texttt{send()} and \texttt{recv()} system calls, while ZMQ takes care of this extra logic involved and even provides higher level messaging patterns such as subscribe-publish, which would need to be implemented on top of raw sockets again. \item Using something like gRPC\footnote{\href{https://grpc.io}{https://grpc.io}} or plain HTTP (both of which are considered to be higher level than ZMQ sockets) would require all processes partaking in the communication to be HTTP servers themselves, which would make the framework less lightweight and flexible: socket communication with or without ZMQ does not force you to write synchronous or asynchronous code, whereas common HTTP servers are either async or pre-fork in nature, which extort certain design choices on code built on them. \end{itemize} \section{High Level Overview} Now being familiar with the technological basis of the framework we can now discuss it in more detail. \pic{figures/tfw_architecture.png}{An overwiew of the Tutorial Framework} Architecturally TFW consists of four main components: \begin{itemize} \item \textbf{Event handlers}: processes running in a Docker container \item \textbf{Frontend}: web application running in the browser of the user \item \textbf{TFW (proxy) server}: responsible for message routing/proxying between the frontend and event handlers \item \textbf{TFW FSM}: a finite state machine responsible for tracking user progress, that is implemented as an event handler called \texttt{FSMManagingEventHandler} \end{itemize} Note that it is important to keep in mind that as I've mentioned previously, the TFW Server and event handlers reside in the \texttt{solvable} Docker container. They all run in separate processes and only communicate using ZeroMQ sockets. In the following sections I am going to explain each of the main components in greater detail, as well as how they interact with each other, their respective responsibilities, some of the design choices behind them and more. \subsection{TFW Message Format} All components in the Tutorial Framework use JSON% \footnote{JavaScript Object Notation: \href{https://www.json.org}{https://www.json.org}} messages to communicate with eachother. These messages must also comply some simple rules specific to the framework. Let's inspect further what a valid TFW message might look like: \begin{lstlisting}[captionpos=b,caption={The TFW JSON message format}] { "key": ...an identifier used for addressing..., "data": { ... optional JSON object carrying arbitrary data ... }, "trigger": ...optional state change action..., "signature": ...optional HMAC signature for authenticated messages..., "seq": ...sequence number that is automatically inserted by the TFW server... } \end{lstlisting} All valid messages \emph{must} include a \texttt{key} field as this is used by the framework for addressing: event handlers and frontend components subscribe to one or more \texttt{key}s and only receive messages with \texttt{key}s they have subscribed to. It is possible to send a message with an empty key, however these messages will not be forwarded by the TFW server (but will reach it, so in case the target of a message is the TFW server exclusively, an empty \texttt{key} can may used). The rest of the fields are optional, but most messages will carry something in their \texttt{data} field. The purpose \texttt{trigger} and \texttt{signature} fields will be detailed at a later point in this paper. \subsection{Networking Details} The default behaviour of the TFW server is that it forwards all messages from coming from the frontend to the event handlers and vice versa. So messages coming from the WebSockets of the frontend are forwarded to event handlers via ZMQ and messages received through ZMQ from event handlers are forwarded to the frontend via WebSockets. The TFW server is also capable of ``reflecting'' messages back to the side they were received on (to faciliate event handler to event handler for instance), or broadcast messages to all components. This is possible by embedding a whole TFW message in the \texttt{data} field of an outer wrapper message with a special \texttt{key} that signals to the TFW server that this message requires special attention. An example of this would be: \begin{lstlisting}[captionpos=b,caption={Broadcasting and mirroring TFW messages}] } "key": "broadcast", // or "mirror" "data": { ... The message you want to broadcast or mirror (with it's own "key" and "data" fields) ... } } \end{lstlisting} Any invalid messages (not valid JSON or no \texttt{key} field present) are ignored by the TFW server. \subsection{Event Handlers} Event handlers are processes running in the \texttt{solvable} Docker container that subscribe to specific message types using ZeroMQ sockets. As discussed earlier, using ZeroMQ allows developers to implement event handlers in a wide variety of programming languages. This is very important for the framework, as content creators often create challenges that are very specific to a language, for example the showcasing of a security vulnerability in an older version of Java. These event handlers are used to write most of the code developers wish to integrate with the framework. Let's say that a tutorial asks the user to fix some piece of legacy C code. In this case, a challenge developer could implement an event handler that runs some unit tests to determine whether the user was successful in fixing the code or not, then advance the tutorial or invoke other event handlers based on this knowledge. An event handler such as this could be invoked by sending a message to it at any time when the running of the tests would be required. \subsection{Frontend} This is a web application that runs in the browser of the user and uses multiple WebSocket connections to connect to the TFW server. Due to rapidly increasing complexity the original implementation (written in plain JavaScript with jQuery% \footnote{\href{https://jquery.com}{https://jquery.com}} and Bootstrap% \footnote{\href{https://getbootstrap.com}{https://getbootstrap.com}}) was becoming unmaintainable and the usage of some frontend framework became justified. Several choices were considered, with the main contenders being: \begin{itemize} \item Angular\footnote{\href{https://angular.io}{https://angular.io}} \item React\footnote{\href{https://reactjs.org}{https://reactjs.org}} \item Vue.js\footnote{\href{https://vuejs.org}{https://vuejs.org}} \end{itemize} After comparing the above frameworks we've decided to work with Angular for several reasons. One being that Angular is essentially a complete platform that is very well suitable for building complex architecture into a single page application. Other reasons included that the frontend of the Avatao platform is also written in Angular (bonus points for experienced team members in the company). An other good thing going for it is that Angular forces you to use TypeScript% \footnote{\href{https://www.typescriptlang.org}{https://www.typescriptlang.org}} which tries to remedy the issues\cite{JavaScript} with JavaScript by being a language that transpiles to JavaScript while strongly encouraging things like static typing or Object Oriented Principles. \pic{figures/tfw_frontend.png}{The Current Angular Frontend of the Tutorial Framework} \subsection{Built-in Event Handlers and Frontend Components} A good chunk of the framework codebase is a bunch of pre-made, built-in components that implement commonly required functionality for developers to use. These components usually involve an event handler and an Angular component which communicates with it to realize some functionality. An example would be the built-in code editor of the framework (visible on the left side of Figure~\ref{figures/tfw_frontend.png}). This code editor is essentially a Monaco editor% \footnote{\href{https://microsoft.github.io/monaco-editor/} {https://microsoft.github.io/monaco-editor/}} instance integrated into Angular and upgraded with the capability to exchanges messages with an event handler to save, read and edit files that reside in the writeable file system of the \texttt{solvable} Docker container. All of the built-ins come with full API documentation explaining what they do on receiving specific messages, and what messages they emit on different events. This greatly expands the capabilities of the framework, since it allows developers to do things including, but not limited to: \begin{itemize} \item making the code editor automatically appear in sections of the tutorial where the user needs to use it \item inject commands into the user's terminal \item hook into messages emitted from components to detect events, such as to detect if the user has clicked a button or executed a command in the terminal \item monitor the logs (stdout or stderr) of a given process \end{itemize} Every pre-made component is designed with the mindset to allow flexible and creative usage by developers, with the possibility of future extensions. Often when developers require certain new features, they open an issue on the git repository of the framework for me to review and possibly implement later. One example would be when a developer wanted to automatically advance the tutorial when the user has entered a specific string into a file. This one didn't even require a new feature: I recommended him to implement an event handler listening to the messages of the built-in file editor, filter the messages which contain file content that is going to be written to disk, and simply search these messages for the given string. The exact capabilities of these built-in components will be explained in greater detail in a later chapter. \subsection{TFW Finite State Machine} An important requirement we have specified during~\ref{requirements} was that the framework must be capable of tracking user progress. TFW allows developers to define a \emph{finite state machine} which is capable of describing the desired ``story'' of a tutorial. The states of the machine could be certain points in time during the completion of the tutorial envisioned and transitions could be events that influence the state, such as the editing of files, execution of commands and so on. Take the fixing of a SQL Injection% \footnote{\href{https://www.owasp.org/index.php/SQL_Injection} {https://www.owasp.org/index.php/SQL\_Injection}} vulnerability as an example. Let's assume, that the source code is vulnerable to a SQL injection attack because it tries to compose a query with string concatenation instead of using a parameterized query provided by the database library. A challenge developer could implement an FSM in the framework that looks like this: \pic[width=.6\textwidth]{figures/tfw_fsm.png}{An Example for a Finite State Machine in TFW} In case the source file has been edited, the unit test cases designed to detect whether the code is vulnerable or not are invoked. Depending on the results three cases are possible: \begin{description} \item[All test cases have succeeded:] If all the tests succeeded then the user has managed to fix the code properly and we can display a congratulating message accordingly. \item[All test cases have failed:] In this case the solution is incorrect and we can offer some hints. \item[Some test cases have succeeded:] It is possible that the based on the test cases that have succeeded and failed we can determine that the user tried to blacklist certain SQL keywords. This is a common, but incorrect solution of fixing a SQL injection vulnerability. Now we can explain to users why their solution is wrong, and give them helpful tips. \end{description} This example shows how content creators can create tutorials that could behave in many different ways based on what the user does. In high quality challenges developers can implement several ``paths'' to a successful completion. This is a very engaging feature that offers an immersive learning experience for users, which many solutions for distance education lack so often. Developers can use a YAML file or write Python code to implement finite state machines. Architecturally a built-in event handler called \texttt{FSMManagingEventHandler} is capable of managing the FSM defined by clients. The responsibilities of said event handler include: \begin{itemize} \item Attempting to step the state machine (one can write preconditions that are required to succeed for the transition to take place) \item Broadcasting FMS update messages on events involving the state machine, such as successful transitions \end{itemize} The \texttt{trigger} field of a message can be used to step the framework FSM if all preconditions are met. The way this works is if the TFW server encounters a message with a \texttt{trigger} defined, it notifies the event handler managing the state machine. Since messages can come from unauthenticated sources, it is possible to enforce the authentication of privileged messages, such as messages containing a \texttt{trigger}. The framework allows trusted code to access a cryptographic key on the file system, which can be used to digitally sign messages (this is what the \texttt{signature} message field is designed for). In this case the TFW server will only forward privileged messages that have a valid signature. \subsection{Solution checking}\label{solutioncheck} Traditionally most challenges on the Avatao platform implement a Docker image called \texttt{controller}, which is responsible for detecting the successful solution of a challenge. When using the Tutorial Framework a pre-implemented \texttt{controller} image is available, which listens to messages emitted by the framework FSM, and detects if the final state defined by developers is reached. This means that if content creators implement a proper FSM, the solution checking does not require any more effort from their part and will work automatically. It is also worth to note that the authentication of privileged messages makes the Tutorial Framework suitable for implementing traditional hacking challenges, such as exercises developed for CTF% \footnote{A ``capture the flag'' game is a competition designed for professionals --- or just people interested in the field --- to sharpen their skills in IT security. Avatao often organises similar events.} events.