Skip to content

Commit 934cd2c

Browse files
committed
[feature] add warning about UID/GID missmatch for docker socket, thanks to @broetchenrackete36, @johnatank
1 parent 49eeeba commit 934cd2c

5 files changed

Lines changed: 79 additions & 8 deletions

File tree

.github/workflows/docker.yml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ jobs:
250250
if: env.WORKFLOW_CREATE_RELEASE == 'true' && steps.git-log.outcome == 'success'
251251
id: git-release
252252
uses: 11notes/action-docker-release@v1
253+
# WHY IS THIS ACTION NOT SHA256 PINNED? SECURITY MUCH?!?!?!
254+
# ---------------------------------------------------------------------------------
255+
# the next step "github / release / create" creates a new release based on the code
256+
# in the repo. This code is not modified and can't be modified by this action.
257+
# It does create the markdown for the release, which could be abused, but to what
258+
# extend? Adding a link to a malicious repo?
253259
with:
254260
git_log: ${{ steps.git-log.outputs.commits }}
255261

@@ -267,6 +273,31 @@ jobs:
267273

268274

269275

276+
277+
# LICENSE
278+
- name: license / update year
279+
continue-on-error: true
280+
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
281+
with:
282+
script: |
283+
const { existsSync, readFileSync } = require('node:fs');
284+
const { resolve } = require('node:path');
285+
const file = 'LICENSE';
286+
try{
287+
const path = resolve(file );
288+
if(existsSync(path)){
289+
let license = readFileSync(file).toString();
290+
license = license.replace(/Copyright \(c\) \d{4} /i, `Copyright (c) ${new Date().getFullYear()} `)
291+
}else{
292+
throw new Error(`file ${file} does not exist`);
293+
}
294+
}catch(e){
295+
core.setFailed(e);
296+
}
297+
298+
299+
300+
270301
# README
271302
- name: github / checkout master
272303
continue-on-error: true
@@ -278,6 +309,13 @@ jobs:
278309
continue-on-error: true
279310
if: env.WORKFLOW_CREATE_README == 'true' && steps.docker-build.outcome == 'success'
280311
uses: 11notes/action-docker-readme@v1
312+
# WHY IS THIS ACTION NOT SHA256 PINNED? SECURITY MUCH?!?!?!
313+
# ---------------------------------------------------------------------------------
314+
# the next step "github / commit & push" only adds the README and LICENSE as well as
315+
# compose.yaml to the repository. This does not pose a security risk if this action
316+
# would be compromised. The code of the app can't be changed by this action. Since
317+
# only the files mentioned are commited to the repo. Sure, someone could make a bad
318+
# compose.yaml, but since this serves only as an example I see no harm in that.
281319
with:
282320
sarif_file: ${{ steps.grype.outputs.sarif }}
283321
build_output_metadata: ${{ steps.docker-build.outputs.metadata }}
@@ -292,6 +330,9 @@ jobs:
292330
if [ -f compose.yaml ]; then
293331
git add compose.yaml
294332
fi
333+
if [ -f LICENSE ]; then
334+
git add LICENSE
335+
fi
295336
git commit -m "auto update README.md"
296337
git push
297338
@@ -307,7 +348,8 @@ jobs:
307348
provider: dockerhub
308349
short_description: ${{ env.DOCKER_IMAGE_DESCRIPTION }}
309350
readme_file: 'README_DOCKER.md'
310-
351+
352+
311353

312354

313355
# REPOSITORY SETTINGS

arch.dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,4 @@
4848
HEALTHCHECK --interval=5s --timeout=2s CMD ["/socket-proxy", "--healthcheck"]
4949

5050
# :: Start
51-
USER root
5251
ENTRYPOINT ["/socket-proxy"]

compose.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
name: "traefik" # this is a compose example for Traefik
22
services:
33
socket-proxy:
4-
image: "11notes/socket-proxy:2.0.0"
4+
image: "11notes/socket-proxy:2.1.0"
5+
user: "0:0" # make sure to use the same UID/GID as the owner of your docker socket!
56
volumes:
67
- "/run/docker.sock:/run/docker.sock:ro" # mount host docker socket, the :ro does not mean read-only for the socket, just for the actual file
78
- "socket-proxy:/run/proxy" # this socket is run as 1000:1000, not as root!

go/socket-proxy/main.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ func prepareFileSystemDropPrivileges(){
4949
log.Fatalf("could not chown folder %s", proxyVolume, err)
5050
}
5151

52+
// check docker socket permissions
53+
stat, err := os.Stat(os.Getenv("SOCKET_PROXY_DOCKER_SOCKET"))
54+
if err != nil {
55+
log.Fatalf("could not evaluate ownership of docker socket, permission issue %v", err)
56+
}
57+
if ownership, ok := stat.Sys().(*syscall.Stat_t); !ok {
58+
log.Fatalf("could not evaluate ownership of docker socket, permission issue %v", err)
59+
}else{
60+
if(int(ownership.Uid) != os.Getuid()){
61+
log.Fatalf("can’t access docker socket as UID %d owned by UID %d\nplease change the user setting in your compose to the correct UID/GID pair like this:\nservices:\n socket-proxy:\n user: \"%d:%d\"", os.Getuid(), ownership.Uid, ownership.Uid, ownership.Gid)
62+
}else{
63+
if(int(ownership.Gid) != os.Getgid()){
64+
log.Fatalf("can’t access docker socket as GID %d owned by GID %d\nplease change the user setting in your compose to the correct UID/GID pair like this:\nservices:\n socket-proxy:\n user: \"%d:%d\"", os.Getgid(), ownership.Gid, os.Getuid(), ownership.Gid)
65+
}
66+
}
67+
}
68+
5269
// drop privileges since only the proxy must access the socket as root and nothing else
5370
if err := syscall.Setgid(proxyGID); err != nil {
5471
log.Fatalf("could not set GID to %d %v", proxyGID, err)
@@ -106,6 +123,7 @@ func main(){
106123
}
107124
os.Exit(0)
108125
}else{
126+
log.Println("starting ...")
109127
// setup signal handler
110128
signals()
111129

@@ -114,27 +132,28 @@ func main(){
114132
proxy = httputil.NewSingleHostReverseProxy(localhost)
115133
proxy.Transport = &http.Transport{
116134
DialContext: func(_ context.Context, _, _ string)(net.Conn, error){
135+
log.Println("starting proxy to docker socket ...")
117136
return net.Dial("unix", os.Getenv("SOCKET_PROXY_DOCKER_SOCKET"))
118137
},
119138
}
120139

121140
// prepare the file system and drop privileges to UID/GID
122141
prepareFileSystemDropPrivileges()
123142

124-
wg.Add(2)
125-
126143
// setup unix to socket proxy
127-
serverUnix := &http.Server{
144+
unixServer := &http.Server{
128145
Handler: http.HandlerFunc(httpProxy),
129146
}
130147
os.Remove(socketProxy)
131148
unix, err := net.Listen("unix", socketProxy)
132149
if err != nil {
133150
log.Fatalf("could not start unix socket %v", err)
134151
}
152+
wg.Add(1)
135153
go func(){
136154
defer wg.Done()
137-
if err := serverUnix.Serve(unix); err != nil {
155+
log.Println("starting proxy UNIX socket ...")
156+
if err := unixServer.Serve(unix); err != nil {
138157
log.Fatalf("could not start unix socket %v", err)
139158
}
140159
}()
@@ -148,8 +167,10 @@ func main(){
148167
if err != nil {
149168
log.Fatalf("could not start tcp socket %v", err)
150169
}
170+
wg.Add(1)
151171
go func(){
152172
defer wg.Done()
173+
log.Println("starting proxy TCP socket ...")
153174
if err := httpServer.Serve(tcp); err != nil {
154175
log.Fatalf("could not start tcp socket %v", err)
155176
}

project.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
${{ content_synopsis }} This image will run a proxy to access your docker socket as read-only. The exposed proxy socket is run as 1000:1000, not as root, although the image starts the proxy process as root to interact with the actual docker socket. There is also a TCP endpoint started at 2375 that will also proxy to the actual docker socket if needed. It is not exposed by default and must be exposed via using ```- "2375:2375/tcp"``` in your compose.
1+
${{ content_synopsis }} This image will run a proxy to access your docker socket as read-only. The exposed proxy socket is run as 1000:1000, not as root, although the image starts the proxy process as root to interact with the actual docker socket. There is also a TCP endpoint started at 2375 that will also proxy to the actual docker socket if needed. It is not exposed by default and must be exposed via using ```- "2375:2375/tcp"``` in your compose. Make sure that the docker socket is accessible by the ```user:``` specification in your compose, if the UID/GID are not correct, the image will print out the correct UID/GID for you to set:
2+
3+
```shell
4+
socket-proxy-1 | 2025/03/26 10:16:33 can’t access docker socket as GID 0 owned by GID 991
5+
socket-proxy-1 | please change the user setting in your compose to the correct UID/GID pair like this:
6+
socket-proxy-1 | services:
7+
socket-proxy-1 | socket-proxy:
8+
socket-proxy-1 | user: "0:991"
9+
```
210

311
${{ content_uvp }} Good question! All the other images on the market that do exactly the same don’t do or offer these options:
412

0 commit comments

Comments
 (0)