Fossil

Diff
Login

Differences From Artifact [5f266938df]:

To Artifact [475ef5bbd8]:


295
296
297
298
299
300
301
302

303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320

321
322
323
324

325
326
327
328















329
330
331
332
333
334
335
295
296
297
298
299
300
301

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319

320
321
322
323

324
325



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347







-
+

















-
+



-
+

-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







<i>image</i>, not in the image itself.

The simplest method is to stop the container if it was running,
then say:

<pre><code>  $ docker cp /path/to/my-project.fossil fossil:/jail/museum/repo.fossil
  $ docker start fossil
  $ docker exec fossil chown 0 /jail/museum/repo.fossil</code></pre>
  $ docker exec fossil chown -R 499 /jail/museum</code></pre>

That copies the local Fossil repo into the container where the server
expects to find it, so that the "start" command causes it to serve from
that copied-in file instead. Since it lives atop the immutable base layers, it
persists as part of the container proper, surviving restarts.

(The same is true of the default mode of operation: the <tt>fossil
server --create</tt> flag initializes a fresh Fossil repo atop the base
image.)

Notice that the name of the repository database likely needs to change
in the copy. The container configuration expects it to be called
<tt>repo.fossil</tt>, which it almost certainly was not out on the host
system. This is because there is only one repository inside this
container, so we don't have to name it after the project it contains, as
is traditional.

If you skip the following "chown" command and put "http://localhost:9999/" into
If you skip the "chown" command above and put "http://localhost:9999/" into
your browser, expecting to see the copied-in repo's home page, you will
get an opaque "Not Found" error. This is because the user and
group ID of the file will be that of your local user on the container's
host machine, which won't map to anything in the container's
host machine, which is unlikely to map to anything in the container's
<tt>/etc/passwd</tt> and <tt>/etc/group</tt> files, effectively
preventing the server from reading the copied-in repository file. You
don't have to restart the server after fixing this with <tt>chmod</tt>: simply reload the
browser, and Fossil will try again.
preventing the server from reading the copied-in repository file.
499 is the default "fossil" user ID inside the container, causing Fossil to run
with that user's privileges after it enters the chroot.
You don't have to restart the server after fixing this with
<tt>chmod</tt>: simply reload the browser, and Fossil will try again.

(Why 499? Because regular user IDs start at 500 or 1000 on most Unix
type systems, leaving those below it for "system" users like this Fossil
daemon owner; standard OS system users start at 0 and go upward, so we
started at 500 and went down one to avoid a conflict. If you use Docker
volumes, you may wish to override this at container creation time — the
"run" or "create" command — by passing an option like "<tt>-e
UID=501</tt>". The internal container user IDs are used for permissions
on the volume, where the host's user/group IDs come into play, so
syncing the in-container user ID with the host can be useful.)

This simple method has a problem: Docker containers are designed to be
killed off at the slightest cause, rebuilt, and redeployed. If you do
that with the repo inside the container, it gets destroyed, too. The
solution is to replace the "run" command above with the following:

<pre><code>  $ docker create \
344
345
346
347
348
349
350
351
352


353
354
355

356

357
358
359
360
361
362
363
364
365
366
367
368
369
370










371
372
373
374
375
376
377
356
357
358
359
360
361
362


363
364
365
366
367
368

369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400







-
-
+
+



+
-
+














+
+
+
+
+
+
+
+
+
+







— the volume remains behind and gets remapped into the new container
when you recreate it by giving the above command again.


<h3>5.2 Why Chroot?</h3>

A potentially surprising feature of this container is that it runs
Fossil as root, which causes [./chroot.md | Fossil's chroot jail
feature] to kick in. Since a Docker container is a type of über-jail
Fossil as root. Since that causes [./chroot.md | Fossil's chroot jail
feature] to kick in, and a Docker container is a type of über-jail
already, you may be wondering why we don't either:

  #  run <tt>fossil server --nojail</tt> to skip the internal chroot; or
  #  set "<tt>USER fossil</tt>" in the Dockerfile so it starts Fossil as
  #  create a non-root user and force Docker to use that instead
     that user instead

The reason is, although this container is quite stripped-down by today's
standards, it's based on the [https://www.busybox.net/BusyBox.html |
surprisingly powerful Busybox project].  (This author made a living for
years in the early 1990s using Unix systems that were less powerful than
this container.) If someone ever figured out how to make a Fossil binary
execute arbitrary commands on the host or to open up a remote shell,
they'd likely be able to island-hop from there into the rest of your
network. We need this cute double-jail dance to keep the Fossil instance
from accessing the Busybox features.

We deem this risk low since a) it's never happened, that we know of;
and b) we've turned off all of the risky features like TH1 docs.
Nevertheless, we believe in defense-in-depth.

If you shell into the container and do a <tt>ps</tt>, you'll find
<tt>fossil</tt> running as <tt>root</tt>. Why? Because that's the parent
process, which may need to do rootly things like listening on port 80 or
443. Fossil's chroot feature takes effect in the child processes forked
off to handle each HTTP/CGI hit. This is why you can fix broken
permissions with <tt>chown</tt> after the container is already running,
without restarting it: each hit reevaluates the repository file
permissions when deciding what user to become when dropping root
privileges.


<h3>5.3 Extracting a Static Binary</h3>

Our 2-stage build process uses Alpine Linux only as a build host. Once
we've got everything reduced to the two key static binaries — Fossil and
Busybox — we throw all the rest of it away.