Skip to content

Commit f5b426d

Browse files
Kern--swagatbora90
andauthored
fix: Make DOCKER_CONFIG available to buildctl (runfinch#94)
When povisioning a finch VM, the CLI inserts a line into the user's bash rc that points DOCKER_CONFIG to the user's finch dir. Finch-daemon running through systemd doesn't load the bash rc and so it doesn't get this config. As a result, build requests that go through finch-daemon don't pass this variable to buildctl so buildctl/buildkit is unable to build on base images in private repositories. This change temporarily fixes this by inserting DOCKER_CONFIG into the finch-daemon process based on the owner of the socket. That way, buildctl is able to load the finch config and retrieve credentials. This should eventually be replaced by somehow passing the credentials that come in to the build API directly to buildctl rather than loading credentials based on the config. Signed-off-by: Kern Walster <[email protected]> Co-authored-by: Swagat Bora <[email protected]>
1 parent 3d6a531 commit f5b426d

File tree

3 files changed

+159
-1
lines changed

3 files changed

+159
-1
lines changed

cmd/finch-daemon/main.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/gofrs/flock"
2828
"github.com/moby/moby/pkg/pidfile"
2929
"github.com/runfinch/finch-daemon/api/router"
30+
"github.com/runfinch/finch-daemon/internal/fs/passwd"
3031
"github.com/runfinch/finch-daemon/pkg/flog"
3132
"github.com/runfinch/finch-daemon/version"
3233
"github.com/sirupsen/logrus"
@@ -149,6 +150,12 @@ func run(options *DaemonOptions) error {
149150
serverWg := &sync.WaitGroup{}
150151
serverWg.Add(1)
151152

153+
if options.socketOwner >= 0 {
154+
if err := defineDockerConfig(options.socketOwner); err != nil {
155+
return fmt.Errorf("failed to get finch config: %w", err)
156+
}
157+
}
158+
152159
listener, err := getListener(options)
153160
if err != nil {
154161
return fmt.Errorf("failed to create a listener: %w", err)
@@ -170,7 +177,6 @@ func run(options *DaemonOptions) error {
170177
}
171178
}()
172179
}
173-
174180
server := &http.Server{
175181
Handler: r,
176182
ReadHeaderTimeout: 5 * time.Minute,
@@ -243,3 +249,19 @@ func sdNotify(state string, logger *flog.Logrus) {
243249
notified, err := daemon.SdNotify(false, state)
244250
logger.Debugf("systemd-notify result: (signaled %t), (err: %v)", notified, err)
245251
}
252+
253+
// defineDockerConfig defines the DOCKER_CONFIG environment variable for the process
254+
// to be $HOME/.finch. When building an image via finch-daemon, buildctl uses this variable
255+
// to load auth configs.
256+
//
257+
// This is a hack and should be fixed by passing the actual credentials that come in
258+
// via the build API to buildctl instead.
259+
func defineDockerConfig(uid int) error {
260+
return passwd.Walk(func(e passwd.Entry) bool {
261+
if e.UID == uid {
262+
os.Setenv("DOCKER_CONFIG", fmt.Sprintf("%s/.finch", e.Home))
263+
return false
264+
}
265+
return true
266+
})
267+
}

internal/fs/passwd/passwd.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package passwd
5+
6+
import (
7+
"bufio"
8+
"io"
9+
"os"
10+
"strconv"
11+
"strings"
12+
)
13+
14+
// open opens /etc/password for reading.
15+
// abstracted so that it can be mocked for tests.
16+
var open = func() (io.ReadCloser, error) {
17+
return os.Open("/etc/passwd")
18+
}
19+
20+
// PasswdEntry represents an entry in /etc/passwd.
21+
type Entry struct {
22+
Username string
23+
UID int
24+
GID int
25+
Home string
26+
Shell string
27+
}
28+
29+
// Walk iterates over all entries in /etc/passwd and calls f for each one
30+
// f returns whether the walk should continue - i.e. returning false from f will
31+
// exit the walk without processing more entries.
32+
func Walk(f func(Entry) bool) error {
33+
file, err := open()
34+
if err != nil {
35+
return err
36+
}
37+
defer file.Close()
38+
s := bufio.NewScanner(file)
39+
for s.Scan() {
40+
entry, err := parse(s.Text())
41+
if err != nil {
42+
return err
43+
}
44+
if !f(entry) {
45+
return nil
46+
}
47+
}
48+
return nil
49+
}
50+
51+
func parse(s string) (Entry, error) {
52+
parts := strings.Split(s, ":")
53+
name := parts[0]
54+
// parts[1] - ignore password info
55+
uid, err := strconv.ParseInt(parts[2], 10, 32)
56+
if err != nil {
57+
return Entry{}, err
58+
}
59+
gid, err := strconv.ParseInt(parts[3], 10, 32)
60+
if err != nil {
61+
return Entry{}, nil
62+
}
63+
// parts[4] - ignore GECOS
64+
home := parts[5]
65+
shell := parts[6]
66+
return Entry{
67+
Username: name,
68+
UID: int(uid),
69+
GID: int(gid),
70+
Home: home,
71+
Shell: shell,
72+
}, nil
73+
}

internal/fs/passwd/passwd_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package passwd
5+
6+
import (
7+
"bytes"
8+
"io"
9+
"strings"
10+
"testing"
11+
12+
"github.com/onsi/ginkgo/v2"
13+
"github.com/onsi/gomega"
14+
)
15+
16+
var (
17+
content = []string{
18+
"root:x:0:0:Super User:/root:/bin/bash",
19+
"sshd:x:74:74:Privilege-separated SSH:/usr/share/empty.sshd:/usr/sbin/nologin",
20+
"user:x:1000:1000::/home/user:/bin/bash",
21+
}
22+
entries = []Entry{
23+
{Username: "root", UID: 0, GID: 0, Home: "/root", Shell: "/bin/bash"},
24+
{Username: "sshd", UID: 74, GID: 74, Home: "/usr/share/empty.sshd", Shell: "/usr/sbin/nologin"},
25+
{Username: "user", UID: 1000, GID: 1000, Home: "/home/user", Shell: "/bin/bash"},
26+
}
27+
)
28+
29+
func TestPasswd(t *testing.T) {
30+
gomega.RegisterFailHandler(ginkgo.Fail)
31+
ginkgo.RunSpecs(t, "UnitTests - passwd parsing")
32+
}
33+
34+
var _ = ginkgo.Describe("passwd", func() {
35+
ginkgo.DescribeTable("when parsing entries",
36+
func(line string, entry Entry) {
37+
parsed, err := parse(line)
38+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
39+
gomega.Expect(parsed).To(gomega.Equal(entry))
40+
},
41+
ginkgo.Entry("should correctly parse root", content[0], entries[0]),
42+
ginkgo.Entry("should correctly parse a daemon user", content[1], entries[1]),
43+
ginkgo.Entry("should correctly parse a regular user", content[2], entries[2]),
44+
)
45+
46+
ginkgo.DescribeTable("when walking passwd file", func(match bool, expectedCount int) {
47+
i := 0
48+
passwdReader := bytes.NewReader([]byte(strings.Join(content, "\n")))
49+
open = func() (io.ReadCloser, error) {
50+
return io.NopCloser(passwdReader), nil
51+
}
52+
Walk(func(e Entry) bool {
53+
gomega.Expect(e).To(gomega.Equal(entries[i]))
54+
i++
55+
return !match
56+
})
57+
gomega.Expect(i).To(gomega.Equal(expectedCount))
58+
},
59+
ginkgo.Entry("should see all entries if no matches", false, len(entries)),
60+
ginkgo.Entry("should see 1 entry if first matches", true, 1),
61+
)
62+
},
63+
)

0 commit comments

Comments
 (0)