What is snowflake?

Snowflake is a system that allows people from all over the world to access censored websites and applications. Similar to how VPNs assist users in getting around Internet censorship, Snowflake helps you avoid being noticed by Internet censors by making your Internet activity appear as though you’re using the Internet for a regular video or voice call.” (see snowflake.torproject.org)

Snowflake can run as browser plugin or as standalone proxy. The standalone proxy can be run as ansible, from source or as docker container. The container was build from the gitlab source of the TOR project and uploaded to docker hub.

The Problem

It is easy to provide a standalone proxy. But in some companies the sources/URLs are not accessible due filters or other reglementations. The idea is to provide an alternative download for the docker image and an automatic deployment of the newest version to a container instance.

The Solution

In order to enable the most automated deployment possible for the Snowflake Proxy, an Azure DevOps Pipeline was created, which retrieves the latest code version of the Docker Files and duplicates it in a GitHub repo. The image is rebuilt and published to Docker Hub tzuehlke/snowflake. In addition, a new container instance is created from the new image and the existing instance is deleted beforehand. The entire pipeline is also checked into the GitHub repo as snowflake-proxy build, publish, create instance YML/JSON.

Step 0: Connections and Variables

The pipeline uses 3 connections:

  • connect2dockerhub: is the connection to docker hub for uploading the new build image
  • snowflake gitlab public: is the public connection to the torproject snowflake gitlab repo
  • VSE2 150: is the connection to azure to deploy the container instance

The connection to github is not used from the service connections. The connection is established via shell code and with secured variables:

Step 1: Get the source

- cron: 10 18 * * 1,5
    - refs/heads/main
  always: true
name: $(Date:yyyyMMdd)$(Rev:.r)
  - repository: self
    type: git
    ref: refs/heads/main

The pipeline is scheduled to run on the first and fifth days of the week at 6:10 p.m. The code is retrieved from the repository of the Snowflake GitLab project. The source is not clean stored in the YAML file and is configured on the GUI as follows:

The service connection does not need username or password.

Step 2: Build Docker Image and Push to Docker Hub

- task: Docker@2
  displayName: 'build image and push to docker hub'
    containerRegistry: connect2dockerhub
    repository: tzuehlke/snowflake
    tags: |

The Docker Task expects a Dockerfile in the files and executes a build and push. It uses the connect2dockerhub connection. The uploaded image is tagged with the current build number $(Build.BuildNumber) and tag latest. It is important that the environment variable DOCKER_BUILDKIT is set to 1, otherwise the error message [error]failed to parse platform : "" is an invalid component of "": platform specifier component must match "^[A-Za-z0-9_-]+$": invalid argument occurs.

Step 3 and 4: Delete and Create Container Instance

- task: AzureCLI@2
  displayName: 'Delete CI'
    azureSubscription: 'VSE2 150 ...'
    scriptType: pscore
    scriptLocation: inlineScript
    inlineScript: 'az container delete --name "snowflake-proxy" --resource-group "rg-snowflake" --yes'
    powerShellErrorActionPreference: silentlyContinue

- task: AzureCLI@2
  displayName: 'Create CI from docker hub'
    azureSubscription: 'VSE2 150 ...'
    scriptType: pscore
    scriptLocation: inlineScript
    inlineScript: 'az container create -g "rg-snowflake" --name "snowflake-proxy" --image tzuehlke/snowflake:latest'
    powerShellErrorActionPreference: silentlyContinue

It uses az container delete and create command to delete and recreate the container instance snowflake-proxy in the resource group rg-snowflake. The powerShellErrorActionPreference: silentlyContinue ensures that the pieline continues to run even if the deletion fails. As source for the Image will tzuehlke/snowflake:latest be used.

Step 5:

- powershell: |
   cd $(Agent.BuildDirectory)
   rm -rf snowflake/
   git config --global user.name $(githubdisplayname)
   git config --global user.email $(githubmail) 
   git clone https://$(githubname):$(githubtoken)@github.com/tzuehlke/snowflake.git
   cd snowflake
   cd ..
   cd s
   ren README.md README_snowflake.md
   copy * $(Agent.BuildDirectory)/snowflake/
   cd ../snowflake
   git add -A
   git commit --allow-empty -m "update $(Build.BuildNumber)"
   git push --force
  errorActionPreference: silentlyContinue
  displayName: 'PS Copy and Push Code '

Logs and Costs

If the container instance is correctly deployed, a log output is generated every hour. It shows how many connections were established and how much data was uploaded and downloaded:

Without further information, a Contsiner instance with one CPU and 1.5GB RAM will be created. This generates costs of €1.22 (divided into CPU and RAM). The total costs per month are around €36.14. In this case, the outgoing traffic was less than 5GB and therefore generated no costs.