SSH Access to Git Repository in Docker

With the likes of Gogs and Gitea, self-hosting a personal git service has become quite common. It’s also not unlikely that the software is run via docker but this brings a problem with regards to SSH access.

Ideally, the git service container just exposes port 22 but this would conflict with the host’s own SSH service. One solution would be to just use different ports for the git SSH and host SSH and that’s perfectly fine. But we can also just have the host SSH service forward the request to the git service itself using the command option in authorized_keys. And as we’ll find out later, the git service itself is using this functionality.

How git over SSH works

When you do a git push, what actually happens is it runs git-send-pack which runs git-receive-pack <directory> on the remote using SSH. The actual communication then just simply happens via stdin/stdout. Conversely, doing a git pull just runs git-fetch-pack and the somewhat confusingly named git-upload-pack on the remote.

So far so good, but did you notice that when cloning via SSH, the remote is typically git@github.com:org/repo? If everyone SSHs in as the git user, how does the git service know which user is which? And how does it prevent users from accessing each other’s repositories?

One typically thinks of authorized_keys as just a list of allowed SSH keys but it can do much more than that. Of particular interest is the command directive which gets run instead of the user supplied command. The original command is passed in as an environment variable SSH_ORIGINAL_COMMAND which can be used to check if we allow it to be run or not.

So with an authorized_keys file like so:

command="verify-user user-1" ssh-rsa ...
command="verify-user user-1" ssh-ed25519 ...
command="verify-user user-2" ssh-rsa ...
command="verify-user user-3" ssh-rsa ...

Each key is tied to a particular user by virtue of command. And verify-user can check the SSH_ORIGINAL_COMMAND if the particular user is allowed access to the particular repository. You can also implement additional restrictions like pull-only or push-only permissions with this setup. This is how both Gogs and Gitea work.

Forwarding git SSH to Docker

When running Gogs on the host, it’s typically run as the git user and when an SSH key is added or removed, it simply rewrites ~git/.ssh/authorized_keys. Thus it just works with the host SSH service without problems. When running inside docker, one thing we can do is bind mount the host’s ~git/.ssh folder into the docker container so that the host can authorize the SSH connections.

The problem lies with the command which only exists inside the docker container itself. So any user trying to connect can authenticate successfully but will get a command not found error. For the Gogs docker image, the command looks like /app/gogs/gogs serv key-1. So we can just make /app/gogs/gogs available on the host and forward the command to the docker container.

Most instructions I’ve seen with regards to this involves using ssh to connect to the internal docker SSH service but this just seems overly complicated to me. If you remember, at it’s core git really only communicates over stdin/stdout and SSH is just a means to get that.

If all we need is for our shim /app/gogs/gogs to be able to run a command inside the docker container with stdin/stdout attached, then we can actually just do that with docker exec. So it can be something like this:

#!/usr/bin/env bash

# Requires the following in sudoers
# git ALL=(ALL) NOPASSWD: /app/gogs/gogs
# Defaults:git env_keep=SSH_ORIGINAL_COMMAND

GOGS_CONTAINER=git-gogs-1

if [[ $EUID -ne 0 ]]; then
  exec sudo "$0" "$@"
fi

if [ "$1" != "serv" ]; then
  exit 1
fi

exec docker exec -i -u git -e "SSH_ORIGINAL_COMMAND=$SSH_ORIGINAL_COMMAND" "$GOGS_CONTAINER" /app/gogs/gogs "$@"

So for git SSH access to Gogs running in docker, the necessary steps here are:

  1. Have a git user on the host
  2. Bind mount ~git/.ssh to /data/git/.ssh in the Gogs container
  3. Add the shim script to /app/gogs/gogs (make sure it’s owned by root and is chmod-ed 0755)
  4. Add the listed sudoers rules
| Comments

Android Multisim Pre-5.1

NOTE if you’re just looking for a library to use, there’s MultiSim. I’ve never used this so I can’t guarantee anything about it. It also only supports SIM information and not SMS.

Phones that can take multiple SIM cards are quite popular in the Philippines. The two major telecoms would have unlimited SMS packages for messages within their networks. It was quite common to have a SIM for each telco and use the appropriate one depending on who you were sending to.

Android’s API only officially supported multiple SIM cards in 5.1 (API level 22) but Android phones with dual-SIM (and even triple-SIM) capabilities were already available at least as far back as 2.3 (API level 10) when I first needed to support it. Since there was no official API for this, the manufacturers just invented their own and of course each one implemented it in a different way.

Mediatek

The first phone we started working on was a Lenovo A60 which used a Mediatek SOC. We somehow got a library from the manufacturer that let us use the dual-SIM functionality, but it was quite a pain to get working as there was limited documentation and we were quite new to Android development at the time.

When we disassembled the library that they gave us, we noticed that the names they used for the additional functions were quite interesting. They were all the TelephonyManager and SmsManager methods with a Gemini suffix and they would take an additional int parameter in addition to the original.

It turned out that these were available on the standard TelephonyManager instance and could be accessed via reflection. The SmsManager was a bit trickier but we ended up figuring out that there was a android.telephony.gemini.GeminiSmsManager class that had the functionality.

In a different phone with a Mediatek SOC, this got renamed to com.mediatek.telephony.gemini.SmsManager for some reason and dropped the Gemini suffix only for the SmsManager.

Intel

It was also around this time that Intel started making SOCs for smartphones. We had an ASUS Fonepad 7. Unlike with the Mediatek device, we didn’t have a library to use here and had to use reflection to find the hidden classes / methods.

What we found was that instead of having a single instance with every method taking a sim parameter, they instead had separate instances of TelephonyManager and SmsManager for each SIM. You would call TelephonyManager.get2ndTm() and SmsManager.get2ndSmsManager() to have access to the 2nd SIM.

Qualcomm

The last phone I looked at was a dual-SIM Moto G. What’s interesting about this one is that the API completely changed in the upgrade from 4.4 to 5.0.

On Android 4.4, the API was pretty close to the Mediatek one. You had a single instance that could dispatch to other SIMs by having an extra parameter on all the methods. These were in android.telephony.MSimTelephonyManager and android.telephony.MSimSmsManager.

On Android 5.0, the API was a weird mix of all the above and also the introduction of android.telephony.SubscriptionManager which was quite close but not exactly the same as what ended up in the official API. Instead of getActiveSubscriptionInfoList there was getActiveSubIdList which only returned long[].

For the information that would normally exist in SubscriptionInfo, you had to query the main TelephonyManager instance which had methods with an extra long parameter for the subscription id. The SmsManager was simpler with just getSmsManagerForSubscriber.

With Android 5.1, I assume they just switched to using the official API so this phone would have gone through 3 different multi-SIM APIs over the course of it’s life.

Epilogue

Around the release of Android 5.1, we stopped work on the app so I never actually got to use the official API myself ironically. We also never really got a big deployment so while I saw quite the variety of multi-SIM implementations, that’s probably not all that’s been out in the wild.

| Comments

ISP Issues

At the first office I worked at, we had 2 different ISPs. This was supposed to be for reliability, as one was fast but spotty, and the other was slow but reliable. Since they weren’t too expensive, we just went and got both.

We have monitoring setup to watch our office IPs from the outside so we could see how often the connection goes down. The interesting thing we found was that the fast and spotty connection had perfect uptime. Even when there was clearly no internet from the office, it was still “up” according to our monitoring.

So we tried pinging our office IP using the other connection and to our surprise it was indeed up. There was even a webserver running on it (we only have VPN exposed). Apparently, it was someone elses CCTV admin page. We could actually see a hallway with people walking by sometimes!

Apparently someone else had our IP address and nothing good comes from an IP conflict. This was completely baffling as our internet line was supposed to be a “business line” and that came with a static IP address. So the only scenarios where this could happen is, the ISP mistakenly gave the same IP to 2 different lines or the ISP allows some clients to freely set their own IP.

We complained to the ISP and eventually got it resolved. They just gave us an entirely new IP address, but they never explained what went wrong. We already had quite a negative opinion of that particular ISP though, and they somehow managed to outdo themselves.

| Comments

Audventure

Sometime around 2013 I wrote a clone of the GBA game bit Generations SoundVoyager called audventure. SoundVoyager is actually a collection of mini-games where sound is the main focus. You can actually play the game blind, and at some point, that’s pretty much what happens.

sound catcher

The signature mini-game in SoundVoyager is sound catcher. In the mini-game, you can only move left and right at the bottom of the stage, while a “sound” falls from the top. Your goal is to catch the sound which is signified by a green dot. When you catch it, the sound or beat becomes part of the BGM and a new dot appears with a different sound.

You can of course use your eyes and move accordingly, but if you put on earphones, you can actually hear where the dot is, either on your left or right, with it getting louder as it gets close to you. As you collect more sounds, the dot gets more and more transparent. Eventually (and this is where it gets fun), you won’t be able to see the sounds anymore and will have to rely mostly on your ears.

You can see what the original game looks like in this video or you can play it under sound safari in audventure.

WebAudio vs Flash

At the time I wrote audventure, only Chrome supported WebAudio. Also, the API looked (and still looks) quite complicated. Flash on the other hand, was starting to die, but still well-supported so I went with that. For the most part, it worked okay though Chrome actually had timing issues when playing sounds. Now, it doesn’t work in any browser. I tried to debug the issues but ultimately ended up just rewriting it to use WebAudio instead.

For the game, I needed to simulate the source of the sound in 2D/3D space. Flash only really gives you stereo panning and volume control. With some maths, we can actually get an acceptable solution. Less importantly, I needed to be able to get frequency data of the currently playing “sound” to pulse the background. For this, I actually had to implement the feature in the Flash library I was using.

With WebAudio, spatial audio is already built-in and you can simply give it the coordinates of the sounds and the listener. There are some other options to tweak, but for the most part, no complex math is needed. Getting frequency data for a sound is also actually built-in and didn’t take too long to integrate.

Overall, I was impressed by how much you can do with WebAudio out-of-the-box. I kind of understand why it’s complicated, but there’s some simple functionality that I wish was included. For example, there is no API to pause and then resume playing an audio buffer. You have to manually save the elapsed time and play from there.

Other mini-games

So far I’ve only actually implemented the sound catcher mini-game. There are around 4 different categories with slight variations in between.

sound catcher / sound slalom

I’ve explained sound catcher a while ago; sound slalom is a minor variation on that. Instead of waiting for the “sound” to reach you, you now have to guide yourself in between 2 “poles” of sound, as in slalom skiing. But this time, you can also accelerate forward. The goal is to finish the course before the time runs out.

sound drive / sound chase

In sound drive, you’re driving against the flow on a 5 lane road. You have to avoid oncoming cars, trucks and animals until you reach the end. You’re allowed to change lanes and accelerate, and the game tracks your best time. Sound chase is pretty much the same, except you’re trying to catch up to a “sound”.

sound cannon

In sound cannon, you’re immobile but can rotate within a 180 degree angle. Your goal is too shoot down “sounds” which are heading your way. If a sound reaches you, it’s game over. You win when you kill all the sounds.

sound picker / sound cock

In sound picker, you can move in a giant square field where various sounds are scattered around. Your goal is to pick up all the sounds within the time limit. Sound cock is similar, except the sounds are chickens and you have to chase them around.

Source Code

If you want to see the source code, you can check it out here. The sound files aren’t in the repo though, since I’m not quite sure about the licensing. If you want to contribute music or sound effects, I’d gladly appreciate it.

| Comments