This page describes the architecture of Yona.
High level overview
On network level, this is how it looks like:
Initial support is limited to mobile devices. These devices will access the internet through a VPN tunnel that is terminated on the web traffic classification server (SmoothWall). The server will not filter, in the sense of blocking sites, but just classify the sites. This classification will happen for HTTP and HTTPS traffic, see this page. The requests will be passed on to the target web server. For requests that in categories of interest to Yona, the classification server will post a message on a queue to the Yona server. The Yona server is responsible for maintaining the user and buddy administration and to inform users about events that conflict the goals defined by the users. The Yona app on the device interacts with the Yona server to see buddy events and everything supported by Yona.
Subsequent sections zoom in on the app, the web traffic classification server and the Yona server.
The mobile app is the only user interface provided for Yona. There are no plans to provide a web site. The mobile app has the following high level responsibilities:
- Provide the user interface for all features provided to Yona users
- Configure the VPN connection
- Reconnect the VPN in case it disconnects
- Send a request to the Yona server to inform the buddy in case:
- The user uninstalls
- The user disables the app
- The user disables or otherwise hampers the VPN connection
The app generates and stores a key in the secret storage of the app, that is required for all interactions with the server.
Web traffic classification server
The heart of this server is SmoothWall. This product is commonly used for web filtering. For Yona, it will be deployed as a classification server. The SmoothWall server provides us with the following features:
- VPN server
- Classification engine
- Man-in-the-middle proxy for HTTPS servers. See this page.
Every request passed through the filter engine is logged in the DansGuardian log. Given the constraints of the Linux OS of the SmoothWall server, we will use a Perl script to filter the events that are of relevance to Yona and post these on the queue to the Yona server. The log file (
access.log) is normally written in a file. Instead of the file, we will create a named pipe and have the Perl script read from it. See an example log file parser here. The named pipe approach is chosen for two reasons: it is a very efficient mechanism that does not required disk reads/writes and it prevents from storing sensitive data.
The Perl script will read all SmoothWall log events and match them with a list of Yona-relevant web site categories. If the category is relevant, a message with the relevant data is posted on the queue to the Yona server. Otherwise, the message is discarded.
The Yona server is responsible for:
- Providing the web services that back the mobile app
- Administration of users and buddy relationship
- Producing notifications in case users access websites that conflict their objectives
Given the objective of Yona to help people that "do the very thing they hate" (Romans 7:15), the system by nature stores sensitive information, so security is a prime concern. For an overview of the various design alternatives considered, consult the page Register a goal conflict. These are the design goals:
- Private information about a user (buddies, goals, devices, messages from buddies, messages about goal conflicts, etc.) should be inaccessible to everyone, including those that have administrative access to the systems
- It should be impossible to build a quantitative understanding of how well a user acts against his own goals, even when having administrative access to the systems
The server design uses a combination of public-key encryption and symmetric-key encryption to accomplish these goals. Symmetric-key encryption is used to encrypt all private information of the user: buddies, goals, devices, etc. Public-key encryption is used to implement a secure messaging system to deliver messages from buddies and messages from the web traffic classification. Every user has two "message boxes", comparable to a classic mailbox with lock:
Everyone can drop a message in the box, but you need the key to take a message out of it. Every use has two of these. One is publicly linked to the user account, so it's technically possible to send a message to a user if their identity is known. The other message box is linked indirectly and used for messages related to web traffic. When a user is provisioned, a VPN account is created for them. The user name of this VPN account is a UUID (named accessor ID) that cannot be linked to a user, Through their private key, the user knows what their accessor ID. but without that private key, that link cannot be established. This can be depicted as follows:
The server maintains a little bit of unencrypted user information: their name, mobile number and a reference to their public message box. The other information is encrypted with the key that is stored on in the secure storage of the device (e.g. Android non-exported content provider). This encrypted data includes the buddy relationships, the defined goals and two private keys to access the two message boxes. Writing into the message boxes can be done through the public key (not depicted, to prevent confusion). For reading, the private key is required, which is part of the encrypted data of the user. The message box for direct messages is linked from the public data of the user, so given a user, it is possible to find the direct message box.
The anonymous message box is linked from the private data of the user, so only the user "knows" their anonymous message box. Besides this, the anonymous message box is also linked to the accessor object, which carries the same ID as the VPN user name. Thus the analysis engine (see below) knows where to post messages when a goal conflict is detected for a given VPN account.
The Yona server has a simple layered architecture:
- REST. This layer exposes the RESTful API used by the mobile app. Follows HATEOAS and HAL (see primer here) to provide an efficient and convenient RESTful API. This layer is implemented through Spring HATEOAS.
- Services. The services are independent of the access technology (today REST, yesterday SOAP) and enable creating users, finding goals, etc. Services define the transaction boundaries and can depend on one another to fulfill their task. The services layer take Data Transfer Objects (DTOs) as inputs and outputs. These DTOs are annotated with Jackson annotations, so the REST layer can directly return the DTOs. The services layer is implemented with the Spring Framework.
- Entities. The entities layer hosts the domain model and the repositories. The implementation technology is JPA and Spring Data.
This is the domain model of the Yona server:
The orange texts and associations represent data that's encrypted with the key stored inside the app. The orange classes represent messages that are encrypted through the private key of the message box containing them.
Here is a short description of every class, in a somewhat logical order:
User. This is the heart of the domain model. It contains the so-called public information of the user. It's called public because it can be accessed without the key of that user. The linked
MessageDestinationrepresents the user's direct message box. The user owns a
UserEncryptedinstance containing the private user data. This is optional. In case a user sends a buddy request to a person that is not known in the system yet, then the
Userobject will already be created, so the connect request message can be stored for that user and the buddy-to-be can be linked to the requesting user.
UserEncrypted. This is actually the other half of the user data. Everything in this object is encrypted with the password/key stored in the app. It contains associations to goals of this user and compositely owns the two message boxes, the accessor belonging to this user and buddies of this user.
Buddy. On object of this class represents another user as buddy for the user owning this instance. I.e. if Richard Quinn requests Bob Dunn to become his buddy, then Richard will own a
Buddyinstance representing Bob to him. If Bob accepts that request, then he will own another
Buddyobject representing Richard to him. Richard's
Buddyinstance (representing Bob) will have the
NotRequested. On Bob's side, the sending and receiving status are reversed. This implies that Bob will receive messages when Richard does things conflicting with his goals, while Richard does not receive such messages for Bob. In general usage patterns, the buddy relationship is symmetrical, but that that is implemented in the app: that will automatically request the revers relationship when a user accepts a buddy request. The
Buddyobject has associations for the linked user and for that user:
- The message destination
- The goals
- The accessor
Goal. This represents a set of web site categories that the user does not want to visit.
Accessor. This is an anonymous representation of a user accessing the system. It is identified with an ID that cannot be linked to a user without the password of that user. The accessor relates to a set of goals of applicable to that user and the message destinations that need to be informed in case the user visits a site conflicting with their goals.
MessageSource. The message box mentioned in the text above is implemented in two classes:
MessageSourceallows fetching messages while decrypting them with the embedded private key. It holds a
MessageDestinationallows sending message to that message box, while encrypting them with the embedded public key. It holds a collection of
GoalConflictMessage. The analysis engine issues a message of this type when it detects that an accessor visited a web site that conflicts with the goals set for that accessor. This message has an association with the conflicted goal and the accessor having the conflict.
BuddyConnectRequestMessage. If a user requests someone to become their buddy, this message is added to the direct messages of that user. It contains a set of data that's private to the requesting user, which is now shared to the buddy-to-be. This data includes: an invitation message and associations to the requesting user, the goals of the requesting user, the accessor representing the requesting user, and the buddy object to which this request relates to.
BuddyConnectResponseMessage. If the user accepts a buddy request, a message of this type is returned to the requestor. It carries information that is private to the new buddy, as well as information that is required to process the connect response correctly. It contains a textual response message, the nickname of the buddy and a status telling whether the request is accepted. Furthermore an association to:
- The user becoming the buddy
- The accessor of that user
Buddyinstance to which this response applies
The cluster architecture of the system (both web traffic classification server and Yona server) is covered on a separate page.