Skip to content

Commit e9aae6e

Browse files
authored
[docker-up] Auto-login dockerd if GITPOD_IMAGE_AUTH is set (#20586)
* [docker-up] Minor fixes and add tests Tool: gitpod/catfood.gitpod.cloud * [image-builder-bob] Tests&fixes from an experimental PR Tool: gitpod/catfood.gitpod.cloud * [supervisor, ws-manager] Write docker credentials into client config file if passed into workspace Tool: gitpod/catfood.gitpod.cloud * [server] Introduce project.settings.enableDockerdAuthentication and expose it on the API Tool: gitpod/catfood.gitpod.cloud * [dashboard] Add "Docker registry authentication" toggle under projects/env vars Tool: gitpod/catfood.gitpod.cloud * [server] Guard project.settings.enableDockerdAuthentication by org write_settings permission Tool: gitpod/catfood.gitpod.cloud * review comments with cleanup + small fixes Tool: gitpod/catfood.gitpod.cloud
1 parent 90f15c4 commit e9aae6e

File tree

27 files changed

+1493
-507
lines changed

27 files changed

+1493
-507
lines changed

components/dashboard/src/repositories/detail/variables/ConfigurationVariableList.tsx

+50-46
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Table, TableBody, TableHead, TableHeader, TableRow } from "@podkit/tabl
1414
import { useListConfigurationVariables } from "../../../data/configurations/configuration-queries";
1515
import { LoadingState } from "@podkit/loading/LoadingState";
1616
import { ConfigurationVariableItem } from "./ConfigurationVariableItem";
17+
import { EnableDockerdAuthentication } from "./EnableDockerdAuthentication";
1718

1819
type Props = {
1920
configuration: Configuration;
@@ -27,52 +28,55 @@ export const ConfigurationVariableList = ({ configuration }: Props) => {
2728
}
2829

2930
return (
30-
<ConfigurationSettingsField>
31-
{showAddVariableModal && (
32-
<AddVariableModal
33-
configurationId={configuration.id}
34-
onClose={() => {
35-
setShowAddVariableModal(false);
36-
}}
37-
/>
38-
)}
39-
<div className="mb-2 flex">
40-
<div className="flex-grow">
41-
<Heading3>Environment variables</Heading3>
42-
<Subheading>Manage repository-specific environment variables.</Subheading>
31+
<>
32+
<ConfigurationSettingsField>
33+
{showAddVariableModal && (
34+
<AddVariableModal
35+
configurationId={configuration.id}
36+
onClose={() => {
37+
setShowAddVariableModal(false);
38+
}}
39+
/>
40+
)}
41+
<div className="mb-2 flex">
42+
<div className="flex-grow">
43+
<Heading3>Environment variables</Heading3>
44+
<Subheading>Manage repository-specific environment variables.</Subheading>
45+
</div>
4346
</div>
44-
</div>
45-
{data.length === 0 ? (
46-
<div className="bg-pk-surface-secondary rounded-xl w-full p-6 flex flex-col justify-center space-y-3">
47-
<span className="font-semi-bold text-base">No environment variables are set</span>
48-
<span className="text-sm text-pk-content-secondary">
49-
All repository-specific environment variables will be visible in prebuilds and optionally in
50-
workspaces for this repository.
51-
</span>
52-
</div>
53-
) : (
54-
<Table>
55-
<TableHeader>
56-
<TableRow>
57-
<TableHead>Name</TableHead>
58-
<TableHead className="w-48">Visibility</TableHead>
59-
<TableHead className="w-16"></TableHead>
60-
</TableRow>
61-
</TableHeader>
62-
<TableBody>
63-
{data.map((variable) => (
64-
<ConfigurationVariableItem
65-
key={variable.id}
66-
configurationId={configuration.id}
67-
variable={variable}
68-
/>
69-
))}
70-
</TableBody>
71-
</Table>
72-
)}
73-
<Button className="mt-4" onClick={() => setShowAddVariableModal(true)}>
74-
Add Variable
75-
</Button>
76-
</ConfigurationSettingsField>
47+
{data.length === 0 ? (
48+
<div className="bg-pk-surface-secondary rounded-xl w-full p-6 flex flex-col justify-center space-y-3">
49+
<span className="font-semi-bold text-base">No environment variables are set</span>
50+
<span className="text-sm text-pk-content-secondary">
51+
All repository-specific environment variables will be visible in prebuilds and optionally in
52+
workspaces for this repository.
53+
</span>
54+
</div>
55+
) : (
56+
<Table>
57+
<TableHeader>
58+
<TableRow>
59+
<TableHead>Name</TableHead>
60+
<TableHead className="w-48">Visibility</TableHead>
61+
<TableHead className="w-16"></TableHead>
62+
</TableRow>
63+
</TableHeader>
64+
<TableBody>
65+
{data.map((variable) => (
66+
<ConfigurationVariableItem
67+
key={variable.id}
68+
configurationId={configuration.id}
69+
variable={variable}
70+
/>
71+
))}
72+
</TableBody>
73+
</Table>
74+
)}
75+
<Button className="mt-4" onClick={() => setShowAddVariableModal(true)}>
76+
Add Variable
77+
</Button>
78+
</ConfigurationSettingsField>
79+
<EnableDockerdAuthentication configuration={configuration} />
80+
</>
7781
);
7882
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Copyright (c) 2025 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { SwitchInputField } from "@podkit/switch/Switch";
8+
import { Heading3, Subheading } from "@podkit/typography/Headings";
9+
import { FC, useCallback } from "react";
10+
import { InputField } from "../../../components/forms/InputField";
11+
import { useToast } from "../../../components/toasts/Toasts";
12+
import { useId } from "../../../hooks/useId";
13+
import { ConfigurationSettingsField } from "../ConfigurationSettingsField";
14+
import { Configuration } from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
15+
import { SquareArrowOutUpRight } from "lucide-react";
16+
import { useConfiguration, useConfigurationMutation } from "../../../data/configurations/configuration-queries";
17+
import Alert from "../../../components/Alert";
18+
19+
type Props = {
20+
configuration: Configuration;
21+
};
22+
export const EnableDockerdAuthentication: FC<Props> = ({ configuration }) => {
23+
const { data } = useConfiguration(configuration.id);
24+
const configurationMutation = useConfigurationMutation();
25+
const { toast } = useToast();
26+
27+
const updateEnableDockerdAuthentication = useCallback(
28+
async (enable: boolean) => {
29+
await configurationMutation.mutateAsync(
30+
{
31+
configurationId: configuration.id,
32+
workspaceSettings: {
33+
enableDockerdAuthentication: enable,
34+
},
35+
},
36+
{
37+
onError: (error) => {
38+
toast(`Failed to update dockerd authentication: ${error.message}`);
39+
},
40+
},
41+
);
42+
},
43+
[configurationMutation, configuration.id, toast],
44+
);
45+
46+
const inputId = useId({ prefix: "enable-dockerd-authentication" });
47+
const isEnabled = data?.workspaceSettings?.enableDockerdAuthentication;
48+
49+
return (
50+
<ConfigurationSettingsField>
51+
<Heading3 className="flex flex-row items-center gap-2">Docker registry authentication</Heading3>
52+
<Subheading className="max-w-lg flex flex-col gap-2">
53+
<span className="flex-1 text-left">
54+
Enable authentication with Docker registries inside of workspaces based on the{" "}
55+
<code>GITPOD_IMAGE_AUTH</code> environment variable.
56+
</span>
57+
58+
<Alert type={"warning"} closable={false} showIcon={true} className="flex rounded p-2 mb-2 w-full">
59+
By enabling this, credentials specified in <code>GITPOD_IMAGE_AUTH</code> will be visible inside all
60+
workspaces on this project.
61+
</Alert>
62+
<a
63+
className="gp-link flex flex-row items-center gap-1"
64+
href="https://www.gitpod.io/docs/configure/repositories/environment-variables#docker-registry-authentication"
65+
target="_blank"
66+
rel="noreferrer"
67+
>
68+
Learn about using private Docker images with Gitpod
69+
<SquareArrowOutUpRight size={12} />
70+
</a>
71+
</Subheading>
72+
<InputField id={inputId}>
73+
<SwitchInputField
74+
id={inputId}
75+
checked={isEnabled}
76+
disabled={configurationMutation.isLoading}
77+
onCheckedChange={updateEnableDockerdAuthentication}
78+
label={isEnabled ? "Auto-login enabled" : "Auto-login disabled"}
79+
/>
80+
</InputField>
81+
</ConfigurationSettingsField>
82+
);
83+
};

components/docker-up/BUILD.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ packages:
55
- go.mod
66
- go.sum
77
- "docker-up/**"
8+
- "dockerd/**"
89
- dependencies.sh
910
deps:
1011
- components/common-go:lib

components/docker-up/docker-up/main.go

+10-98
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ package main
99

1010
import (
1111
"archive/tar"
12-
"bufio"
1312
"compress/gzip"
1413
"context"
1514
"embed"
16-
"encoding/json"
1715
"fmt"
1816
"io"
1917
"os"
@@ -25,6 +23,7 @@ import (
2523
"syscall"
2624
"time"
2725

26+
"github.com/gitpod-io/gitpod/docker-up/dockerd"
2827
"github.com/rootless-containers/rootlesskit/pkg/sigproxy"
2928
sigproxysignal "github.com/rootless-containers/rootlesskit/pkg/sigproxy/signal"
3029
"github.com/sirupsen/logrus"
@@ -45,6 +44,7 @@ var opts struct {
4544
UserAccessibleSocket bool
4645
Verbose bool
4746
DontWrapNetNS bool
47+
AutoLogin bool
4848
}
4949

5050
//go:embed docker.tgz
@@ -58,6 +58,7 @@ var aptUpdated = false
5858
const (
5959
dockerSocketFN = "/var/run/docker.sock"
6060
gitpodUserId = 33333
61+
gitpodGroupId = 33333
6162
containerIf = "eth0"
6263
)
6364

@@ -73,6 +74,7 @@ func main() {
7374
pflag.BoolVar(&opts.AutoInstall, "auto-install", true, "auto-install prerequisites (docker)")
7475
pflag.BoolVar(&opts.UserAccessibleSocket, "user-accessible-socket", true, "chmod the Docker socket to make it user accessible")
7576
pflag.BoolVar(&opts.DontWrapNetNS, "dont-wrap-netns", os.Getenv("WORKSPACEKIT_WRAP_NETNS") == "true", "wrap the Docker daemon in a network namespace")
77+
pflag.BoolVar(&opts.AutoLogin, "auto-login", false, "use content of GITPOD_IMAGE_AUTH to automatically login with the docker daemon")
7678
pflag.Parse()
7779

7880
logger := logrus.New()
@@ -118,7 +120,8 @@ func runWithinNetns() (err error) {
118120
)
119121
}
120122

121-
userArgs, err := userArgs()
123+
userArgsValue, _ := os.LookupEnv(DaemonArgs)
124+
userArgs, err := dockerd.ParseUserArgs(log, userArgsValue)
122125
if err != nil {
123126
return xerrors.Errorf("cannot add user supplied docker args: %w", err)
124127
}
@@ -192,98 +195,6 @@ func runWithinNetns() (err error) {
192195
return nil
193196
}
194197

195-
type ConvertUserArg func(arg, value string) ([]string, error)
196-
197-
var allowedDockerArgs = map[string]ConvertUserArg{
198-
"remap-user": convertRemapUser,
199-
}
200-
201-
func userArgs() ([]string, error) {
202-
userArgs, exists := os.LookupEnv(DaemonArgs)
203-
args := []string{}
204-
if !exists {
205-
return args, nil
206-
}
207-
208-
var providedDockerArgs map[string]string
209-
if err := json.Unmarshal([]byte(userArgs), &providedDockerArgs); err != nil {
210-
return nil, xerrors.Errorf("unable to deserialize docker args: %w", err)
211-
}
212-
213-
for userArg, userValue := range providedDockerArgs {
214-
converter, exists := allowedDockerArgs[userArg]
215-
if !exists {
216-
continue
217-
}
218-
219-
if converter != nil {
220-
cargs, err := converter(userArg, userValue)
221-
if err != nil {
222-
return nil, xerrors.Errorf("could not convert %v - %v: %w", userArg, userValue, err)
223-
}
224-
args = append(args, cargs...)
225-
226-
} else {
227-
args = append(args, "--"+userArg, userValue)
228-
}
229-
}
230-
231-
return args, nil
232-
}
233-
234-
func convertRemapUser(arg, value string) ([]string, error) {
235-
id, err := strconv.Atoi(value)
236-
if err != nil {
237-
return nil, err
238-
}
239-
240-
for _, f := range []string{"/etc/subuid", "/etc/subgid"} {
241-
err := adaptSubid(f, id)
242-
if err != nil {
243-
return nil, xerrors.Errorf("could not adapt subid files: %w", err)
244-
}
245-
}
246-
247-
return []string{"--userns-remap", "gitpod"}, nil
248-
}
249-
250-
func adaptSubid(oldfile string, id int) error {
251-
uid, err := os.Open(oldfile)
252-
if err != nil {
253-
return err
254-
}
255-
256-
newfile, err := os.Create(oldfile + ".new")
257-
if err != nil {
258-
return err
259-
}
260-
261-
mappingFmt := func(username string, id int, size int) string { return fmt.Sprintf("%s:%d:%d\n", username, id, size) }
262-
263-
if id != 0 {
264-
newfile.WriteString(mappingFmt("gitpod", 1, id))
265-
newfile.WriteString(mappingFmt("gitpod", gitpodUserId, 1))
266-
} else {
267-
newfile.WriteString(mappingFmt("gitpod", gitpodUserId, 1))
268-
newfile.WriteString(mappingFmt("gitpod", 1, gitpodUserId-1))
269-
newfile.WriteString(mappingFmt("gitpod", gitpodUserId+1, 32200)) // map rest of user ids in the user namespace
270-
}
271-
272-
uidScanner := bufio.NewScanner(uid)
273-
for uidScanner.Scan() {
274-
l := uidScanner.Text()
275-
if !strings.HasPrefix(l, "gitpod") {
276-
newfile.WriteString(l + "\n")
277-
}
278-
}
279-
280-
if err = os.Rename(newfile.Name(), oldfile); err != nil {
281-
return err
282-
}
283-
284-
return nil
285-
}
286-
287198
var prerequisites = map[string]func() error{
288199
"dockerd": installDocker,
289200
"docker-compose": installDockerCompose,
@@ -353,7 +264,8 @@ func installDocker() error {
353264
}
354265

355266
switch hdr.Typeflag {
356-
case tar.TypeReg, tar.TypeRegA:
267+
268+
case tar.TypeReg, tar.TypeRegA: //lint:ignore SA1019 backwards compatibility
357269
file, err := os.OpenFile(dstpath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, mode)
358270
if err != nil {
359271
return xerrors.Errorf("unable to create file: %v", err)
@@ -480,12 +392,12 @@ func detectRuncVersion(output string) (major, minor int, err error) {
480392

481393
major, err = strconv.Atoi(n[0])
482394
if err != nil {
483-
return 0, 0, xerrors.Errorf("could not parse major %s: %w", n[0])
395+
return 0, 0, xerrors.Errorf("could not parse major %s: %w", n[0], err)
484396
}
485397

486398
minor, err = strconv.Atoi(n[1])
487399
if err != nil {
488-
return 0, 0, xerrors.Errorf("could not parse minor %s: %w", n[1])
400+
return 0, 0, xerrors.Errorf("could not parse minor %s: %w", n[1], err)
489401
}
490402

491403
return major, minor, nil

0 commit comments

Comments
 (0)