普通文本  |  283行  |  10.26 KB

Design
======


Overview
--------
Allows trying out Skia code in the browser.


Security
--------

We're putting a C++ compiler on the web, and promising to run the results of
user submitted code, so security is a large concern. Security is handled in a
layered approach, using a combination of seccomp-bpf, chroot jail and rlimits.

*seccomp-bpf* - Used to limit the types of system calls that the user code can
make. Any attempts to make a system call that isn't allowed causes the
application to terminate immediately.

*chroot jail* - The code is run in a chroot jail, making the rest of the
operating system files unreachable from the running code.

*rlimits* - Used to limit the resources the running code can get access to,
for example runtime is limited to 5s of CPU.

User submitted code is also restricted in the following ways:
  * Limited to 10K of code total.
  * No preprocessor use is allowed (no lines can begin with #includes).


Architecture
------------

The server runs on GCE, and consists of a Go Web Server that calls out to the
c++ compiler and executes code in a chroot jail. See the diagram below:

    +–––––––––––––+
    |             |
    |  Browser    |
    |             |
    +––––––+––––––+
           |
    +––––––+––––––+
    |             |
    |             |
    | Web Server  |
    |             |
    |   (Go)      |
    |             |
    |             |
    +–––––––+–––––+
            |
    +–––––––+––––––––––+
    | chroot jail      |
    |  +––––––––––––––+|
    |  | seccomp      ||
    |  |  +––––––––––+||
    |  |  |User code |||
    |  |  |          |||
    |  |  +––––––––––+||
    |  +––––––––––––––+|
    |                  |
    +––––––––––––––––––+

The user code is expanded into a simple template and linked against libskia
and a couple other .o files that contain main() and the code that sets up the
seccomp and rlimit restrictions. This code also sets up the SkCanvas that is
handed to the user code. Any code the user submits is restricted to running in
a single function that looks like this:


    void draw(SkCanvas* canvas) {
      // User code goes here.
    }

The user code is tracked by taking an MD5 hash of the code The template is
expanded out into <hash>.cpp, which is compiled into <hash>.o, which is then
linked together with all the other libs and object files to create an
executable named <hash>.  That executable is copied into a directory
/home/webtry/inout, that is accessible to both the web server and the schroot
jail. The application is then run in the schroot jail, writing its response,
<hash>.png, out into the same directory, /home/webtry/inout/, where is it read
by the web server and returned to the user.

Startup and config
------------------
The server is started and stopped via:

    sudo /etc/init.d/webtry [start|stop|restart]

By sysv init only handles starting and stopping a program once, so we use
Monit to monitor the application and restart it if it crashes. The config
is in:

    /etc/monit/conf.d/webtry

The chroot jail is implemented using schroot, its configuration
file is found in:

    /etc/schroot/chroot.d/webtry

The seccomp configuration is in main.cpp and only allows the following system
calls:

    exit_group
    exit
    fstat
    read
    write
    close
    mmap
    munmap
    brk

Database
--------

Code submitted is stored in an SQL database so that it can be referenced
later, i.e. we can let users bookmark their SkFiddles.

The storage layer will be Cloud SQL (a cloud version of MySQL). Back of the
envelope estimates of traffic come out to a price of a about $1/month.

All passwords for MySQL are stored in valentine.

To connect to the database from the skia-webtry-b server:

    $ mysql --host=173.194.83.52 --user=root --password

Initial setup of the database, the user, and the only table:

    CREATE DATABASE webtry;
    USE webtry;
    CREATE USER 'webtry'@'%' IDENTIFIED BY '<password is in valentine>';
    GRANT SELECT, INSERT, UPDATE ON webtry.webtry        TO 'webtry'@'%';
    GRANT SELECT, INSERT, UPDATE ON webtry.workspace     TO 'webtry'@'%';
    GRANT SELECT, INSERT, UPDATE ON webtry.workspacetry  TO 'webtry'@'%';
    GRANT SELECT, INSERT, UPDATE ON webtry.source_images TO 'webtry'@'%';

    // If this gets changed also update the sqlite create statement in webtry.go.

    CREATE TABLE webtry (
      code               TEXT      DEFAULT ''                 NOT NULL,
      create_ts          TIMESTAMP DEFAULT CURRENT_TIMESTAMP  NOT NULL,
      hash               CHAR(64)  DEFAULT ''                 NOT NULL,
      source_image_id    INTEGER   DEFAULT 0                  NOT NULL,
      PRIMARY KEY(hash),

      FOREIGN KEY (source) REFERENCES sources(id)
    );

    CREATE TABLE workspace (
      name      CHAR(64)  DEFAULT ''                 NOT NULL,
      create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP  NOT NULL,
      PRIMARY KEY(name),
    );

    CREATE TABLE workspacetry (
      name             CHAR(64)  DEFAULT ''                 NOT NULL,
      create_ts        TIMESTAMP DEFAULT CURRENT_TIMESTAMP  NOT NULL,
      hash             CHAR(64)  DEFAULT ''                 NOT NULL,
      source_image_id  INTEGER   DEFAULT 0                  NOT NULL,
      hidden           INTEGER   DEFAULT 0                  NOT NULL,

      FOREIGN KEY (name)   REFERENCES workspace(name),
    );

    CREATE TABLE source_images (
      id        INTEGER     PRIMARY KEY                NOT NULL AUTO_INCREMENT,
      image     MEDIUMBLOB  DEFAULT ''                 NOT NULL, -- Stored as PNG.
      width     INTEGER     DEFAULT 0                  NOT NULL,
      height    INTEGER     DEFAULT 0                  NOT NULL,
      create_ts TIMESTAMP   DEFAULT CURRENT_TIMESTAMP  NOT NULL,
      hidden    INTEGER     DEFAULT 0                  NOT NULL
    );

    ALTER TABLE webtry       ADD COLUMN source_image_id INTEGER DEFAULT 0 NOT NULL AFTER hash;
    ALTER TABLE workspacetry ADD COLUMN source_image_id INTEGER DEFAULT 0 NOT NULL AFTER hash;

Common queries webtry.go will use:

    INSERT INTO webtry (code, hash) VALUES('int i = 0;...', 'abcdef...');

    SELECT code, create_ts, hash FROM webtry WHERE hash='abcdef...';

    SELECT code, create_ts, hash FROM webtry ORDER BY create_ts DESC LIMIT 2;

    // To change the password for the webtry sql client:
    SET PASSWORD for 'webtry'@'%' = PASSWORD('<password is in valentine>');

    // Run before and after to confirm the password changed:
    SELECT Host, User, Password FROM mysql.user;

Common queries for workspaces:

    SELECT hash, create_ts FROM workspace ORDER BY create_ts DESC;

    INSERT INTO workspace (name, hash) VALUES('autumn-river-12354', 'abcdef...');

    SELECT name FROM workspace GROUP BY name;

Common queries for sources:

    SELECT id, image, width, height, create_ts FROM source_images ORDER BY create_ts DESC LIMIT 100;

Password for the database will be stored in the metadata instance, if the
metadata server can't be found, i.e. running locally, then a local sqlite
database will be used. To see the current password stored in metadata and the
fingerprint:

    gcutil  --project=google.com:skia-buildbots    getinstance skia-webtry-b

To set the mysql password that webtry is to use:

    gcutil  --project=google.com:skia-buildbots   setinstancemetadata skia-webtry-b --metadata=password:'[mysql client webtry password]' --fingerprint=[some fingerprint]

To retrieve the password from the running instance just GET the right URL from
the metadata server:

    curl "http://metadata/computeMetadata/v1/instance/attributes/password" -H "X-Google-Metadata-Request: True"

N.B. If you need to change the MySQL password that webtry uses, you must change
it both in MySQL and the value stored in the metadata server.

Source Images
-------------

For every try the user can select an optional source image to use as an input.
The id of the source image is just an integer and is stored in the database
along with the other try information, such as the code.

The actual image itself is also stored in a separate table, 'sources', in the
database.  On startup we check that all the images are available in 'inout',
and write out the images if not. Since they are all written to 'inout' we can
use the same /i/ image handler to serve them.

When a user uploads an image it is decoded and converted to PNG and stored
as a binary blob in the database.

The bitmap is available to user code as a module level variable:

    SkBitmap source;

The bitmap is read, decoded and stored in source before the seccomp jail is
instantiated.


Squid
-----

Squid is configured to run on port 80 and run as an accelerator for the actual
Go program which is running on port 8000. The config for the squid proxy is
held in sys/webtry_squid, which is copied into place during installation and
squid is kept running via monit.

Workspaces
----------

Workspaces are implemented by the workspace and workspacetry tables. The
workspace table keeps the unique list of all workspaces. The workspacetry table
keeps track of all the tries that have occured in a workspace. Right now the
hidden column of workspacetry is not used, it's for future functionality.

Code Editor
-----------
[CodeMirror](http://codemirror.net/) is used for rich code editing. The
following files are included from the official CodeMirror distribution and can
be updated in place (no local customizations):

  * codemirror.js  - base CM implementation
  * codemirror.css - base CM stylesheet
  * clike.js       - C-like syntax highlighting support

Alternatively, we may consider pulling CM as an external dependency at some
point.

Installation
------------
See the README file.