<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex</title>
    <description>The latest articles on DEV Community by Alex (@alvic).</description>
    <link>https://dev.to/alvic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1754241%2Fd92a0f6e-b9e2-4bf2-b59b-57aded3c9d7d.jpeg</url>
      <title>DEV Community: Alex</title>
      <link>https://dev.to/alvic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alvic"/>
    <language>en</language>
    <item>
      <title>Ephemeral self-hosted GitHub actions runners</title>
      <dc:creator>Alex</dc:creator>
      <pubDate>Thu, 15 Aug 2024 10:35:10 +0000</pubDate>
      <link>https://dev.to/alvic/ephemeral-self-hosted-github-actions-runners-42ma</link>
      <guid>https://dev.to/alvic/ephemeral-self-hosted-github-actions-runners-42ma</guid>
      <description>&lt;p&gt;Many people who use GitHub not only as a code repository but also as a CI system have encountered the need to run CI operations on their own machines. This article covers how to run actions runner in docker without saving state and with rootless-docker.&lt;/p&gt;

&lt;p&gt;To run actions-runner in Docker, we'll use the following solution: &lt;a href="https://github.com/myoung34/docker-github-actions-runner" rel="noopener noreferrer"&gt;https://github.com/myoung34/docker-github-actions-runner&lt;/a&gt;. It's basically a bash wrapper over the binary from GitHub, which works quite well. The first thing to look at is the security section of the project's &lt;a href="https://github.com/myoung34/docker-github-actions-runner?tab=readme-ov-file#security" rel="noopener noreferrer"&gt;readme&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Security

It is known that environment variables are not safe from
 exfiltration. If you are using this runner make sure that any
 workflow changes are gated by a verification process (in the actions
 settings) so that malicious PR's cannot exfiltrate these.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it's really true, just run a simple action using this self-hosted runner to see for yourself (might not work, depends on installation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted-runner-default&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ready_for_review&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;basic-info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test env vars&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sudo xargs -0 -L1 -a /proc/1/environ | grep APP_ID &amp;amp;&amp;amp; echo "APP_ID found" || echo "APP_ID not found"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result we can see the APP_ID (I removed the ID itself from the screenshot):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdk45mbztpruvx7jske6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdk45mbztpruvx7jske6.png" alt="APP_ID found" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see, it is easy for an attacker to obtain an access token or application credentials. &lt;br&gt;
For convenience, the wrapper generates the token in the container itself, so sensitive data remains in the container. However, it is possible to solve this problem by moving the token retrieval scripts out of the container. &lt;/p&gt;

&lt;p&gt;The next important step is the ability to run docker-in-docker. By default, the repo offers to throw a docker socket inside the container or run the container in the privileged mode. Running docker as root without the &lt;code&gt;userns-remap&lt;/code&gt; option essentially gives the process root privileges out of the box. For example, you can run the container inside of the action with the &lt;code&gt;-v /:/host&lt;/code&gt; option and access the host filesystem. If you do enable userns-remap, getting docker.sock to work will be quite a challenge.&lt;/p&gt;

&lt;p&gt;To solve this problem, we can run another docker container in parallel, which will run docker-daemon and provide its socket to the github-runner container. Unfortunately, simply passing a socket through a shared volume is not enough, and we are helped by the actions-runner-controller, or rather a comment in one of its PRs: &lt;a href="https://github.com/actions/actions-runner-controller/pull/2919#issuecomment-1737868125" rel="noopener noreferrer"&gt;https://github.com/actions/actions-runner-controller/pull/2919#issuecomment-1737868125&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This comment shows you what settings you need to make before you can run the runner and the non-privileged dind-container on it. &lt;br&gt;
There are two ways to run the runner:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a github-runner user on the host and add it to the docker group. Then run the dind-rootless container in &lt;code&gt;--privileged&lt;/code&gt; mode. Since we are running the container as a rootless user, the &lt;code&gt;--privileged&lt;/code&gt; flag will do no harm.&lt;/li&gt;
&lt;li&gt;Create a github-runner user on the host, run docker-daemon from it (&lt;a href="https://docs.docker.com/engine/security/rootless/#install" rel="noopener noreferrer"&gt;https://docs.docker.com/engine/security/rootless/#install&lt;/a&gt;) and then run dind-rootless.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second option seems preferable to me, as even the parent process is run by the user, not root. All that remains is to automate all this, wrap it in a systemd unit and run it.&lt;/p&gt;

&lt;p&gt;Scripts can be found in my github repo: &lt;a href="https://github.com/alvicsam/github-runner-docker-ephemeral" rel="noopener noreferrer"&gt;https://github.com/alvicsam/github-runner-docker-ephemeral&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Few assumptions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scripts work only for runners that are registered with a github app&lt;/li&gt;
&lt;li&gt;The default storage driver for dind is &lt;code&gt;vfs&lt;/code&gt;, which is slow if you have an image with many layers. There is a &lt;a href="https://github.com/alvicsam/github-runner-docker-ephemeral/blob/master/bin/pre-start.sh#L57-L58" rel="noopener noreferrer"&gt;comment&lt;/a&gt; to enable &lt;code&gt;fuse-overlayfs&lt;/code&gt; which makes dind faster. Unfortunately, I couldn't get it to work with the &lt;code&gt;overlay2&lt;/code&gt; storage driver (as far as I know, it requires permission to use &lt;code&gt;mount()&lt;/code&gt;, which the github-runner user doesn't have).&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
