Published on: November 20, 2024
6 min read
Learn how to allow support for sudo commands using Sysbox, Kata Containers, and user namespaces in this easy-to-follow tutorial.
A development environment often requires sudo permissions to install, configure, and use dependencies during runtime. GitLab now allows secure sudo access for GitLab Remote Development workspaces. This tutorial shows you how to enable GitLab workspace users to securely use sudo commands to perform common tasks.
For the sake of this article, say your project is as simple as the below code.
package main
import (
"encoding/json"
"log/slog"
"net/http"
"os"
)
func main() {
// Set up JSON logger
logFile, err := os.OpenFile("server.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
defer logFile.Close()
jsonHandler := slog.NewJSONHandler(logFile, nil)
logger := slog.New(jsonHandler)
slog.SetDefault(logger)
// Define handlers
http.HandleFunc("/path1", handleRequest)
http.HandleFunc("/path2", handleRequest)
// Start server
slog.Info("Starting server on :3000")
err = http.ListenAndServe(":3000", nil)
if err != nil {
slog.Error("Server failed to start", "error", err)
}
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
data := make(map[string]interface{})
for k, v := range r.Header {
data[k] = v
}
data["method"] = r.Method
data["url"] = r.URL.String()
data["remote_addr"] = r.RemoteAddr
response, err := json.MarshalIndent(data, "", " ")
if err != nil {
slog.Error("Failed to marshal metadata", "error", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Log the metadata
slog.Info("Request received",
"path", r.URL.Path,
"response", string(response),
)
// Write response
w.Header().Set("Content-Type", "application/json")
w.Write(response)
}
This code starts an HTTP server on port 3000, exposes two paths: path1
and path2
. Each HTTP request received is logged to a file server.log
.
Let's run this code with go run main.go
and generate some requests.
i=1
while [ "$i" -le 100 ]; do
echo "Iteration $i"
if [ $((random_number % 2)) -eq 0 ]; then
curl "localhost:3000/path1"
else
curl "localhost:3000/path2"
fi
i=$((i + 1))
done
As you work on this application, you realize the need to analyze the logs to debug an issue. You look at the log file and it is long to parse with a simple glance. You remember there is a handy tool, jq, which parses JSON data. But your workspace does not have it installed.
You want to install jq
through the package manager for this workspace only.
sudo apt update
sudo apt install jq
The output is:
sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
sudo: If sudo is running in a container, you may need to adjust the container configuration to disable the flag.
This happens because GitLab workspaces explicitly disallows sudo
access to prevent privilege escalation on the Kubernetes host.
Now, there is a more secure way to run sudo
commands in a workspace.
That is exactly what we have unlocked in the 17.4 release of GitLab.
You can configure secure sudo access for workspaces using any of the following options:
We will set up three GitLab agents for workspaces to demonstrate each option.
Sysbox is a container runtime that improves container isolation and enables containers to run the same workloads as virtual machines.
To configure sudo access for a workspace with Sysbox:
remote_development:
enabled: true
dns_zone: "sysbox-update.me.com"
default_runtime_class: "sysbox-runc"
allow_privilege_escalation: true
annotations:
"io.kubernetes.cri-o.userns-mode": "auto:size=65536"
Kata Containers is a standard implementation of lightweight virtual machines that perform like containers but provide the workload isolation and security of virtual machines.
To configure sudo access for a workspace with Kata Containers:
remote_development:
enabled: true
dns_zone: "kata-update.me.com"
default_runtime_class: "kata-qemu"
allow_privilege_escalation: true
User namespaces isolate the user running inside the container from the user on the host.
To configure sudo access for a workspace with user namespaces:
remote_development:
enabled: true
dns_zone: "userns-update.me.com"
use_kubernetes_user_namespaces: true
allow_privilege_escalation: true
Setting up a Kubernetes cluster with user namespaces configured is challenging since it is behind a beta feature gate in Kubernetes Version 1.31.0. This means it is not yet possible to configure such a cluster on the major cloud providers because they don't provide a mechanism to enable feature gates in their managed Kubernetes offering. Here is an example of configuring a simple Kuberenetes cluster using kubeadm
.
If you now create a workspace with these agents and try installing jq
through a package manager, it should succeed!
You can analyze the logs using jq
. Say you wanted to inspect the log entries where the path is /path1
, you can run:
jq 'select(.path == "/path1")' server.log
The output is:
{
"time": "2024-10-31T12:04:38.474806+05:30",
"level": "INFO",
"msg": "Request received",
"path": "/path1",
"response": "{\n \"Accept\": [\n \"*/*\"\n ],\n \"User-Agent\": [\n \"curl/8.7.1\"\n ],\n \"method\": \"GET\",\n \"remote_addr\": \"[::1]:61246\",\n \"url\": \"/path1\"\n}"
}
{
"time": "2024-10-31T12:06:22.397453+05:30",
"level": "INFO",
"msg": "Request received",
"path": "/path1",
"response": "{\n \"Accept\": [\n \"*/*\"\n ],\n \"User-Agent\": [\n \"curl/8.7.1\"\n ],\n \"method\": \"GET\",\n \"remote_addr\": \"[::1]:61311\",\n \"url\": \"/path1\"\n}"
}
{
"time": "2024-10-31T12:19:34.974354+05:30",
"level": "INFO",
"msg": "Request received",
"path": "/path1",
"response": "{\n \"Accept\": [\n \"*/*\"\n ],\n \"User-Agent\": [\n \"curl/8.7.1\"\n ],\n \"method\": \"GET\",\n \"remote_addr\": \"[::1]:61801\",\n \"url\": \"/path1\"\n}"
}
Learn even more with our Configure sudo access for a workspace documentation. See GitLab agent for workspaces settings for details on individual settings.
New to GitLab Remote Development? Here is a quickstart guide to get you up to speed.