Skip to content

Commit 8248935

Browse files
csweichelroboquat
authored andcommitted
[ws-manager] Add Git token to workspace secret
1 parent f1bc89c commit 8248935

File tree

11 files changed

+368
-321
lines changed

11 files changed

+368
-321
lines changed

components/content-service-api/go/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ go 1.18
44

55
require (
66
github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000
7+
github.com/google/go-cmp v0.5.7
78
github.com/opencontainers/go-digest v1.0.0
89
github.com/opencontainers/image-spec v1.0.2
10+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
911
google.golang.org/grpc v1.45.0
1012
google.golang.org/protobuf v1.28.0
1113
)
@@ -33,7 +35,6 @@ require (
3335
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
3436
golang.org/x/text v0.3.7 // indirect
3537
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
36-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
3738
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 // indirect
3839
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
3940
)

components/content-service-api/go/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
120120
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
121121
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
122122
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
123+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
123124
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
124125
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
125126
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package api
6+
7+
import (
8+
"fmt"
9+
"strconv"
10+
"strings"
11+
12+
"golang.org/x/xerrors"
13+
)
14+
15+
// GetCheckoutLocationsFromInitializer returns a list of all checkout locations from the initializer
16+
func GetCheckoutLocationsFromInitializer(init *WorkspaceInitializer) []string {
17+
var res []string
18+
_ = WalkInitializer(nil, init, func(path []string, init *WorkspaceInitializer) error {
19+
switch spec := init.Spec.(type) {
20+
case *WorkspaceInitializer_Git:
21+
res = append(res, spec.Git.CheckoutLocation)
22+
case *WorkspaceInitializer_Backup:
23+
res = append(res, spec.Backup.CheckoutLocation)
24+
25+
case *WorkspaceInitializer_Prebuild:
26+
// walkInitializer will visit the Git initializer
27+
}
28+
29+
return nil
30+
})
31+
return res
32+
}
33+
34+
const extractedSecretPrefix = "extracted-secret/"
35+
36+
// ExtractSecretsFromInitializer removes secrets to the initializer.
37+
// This is the counterpart of InjectSecretsToInitializer.
38+
func ExtractSecretsFromInitializer(init *WorkspaceInitializer) map[string]string {
39+
res := make(map[string]string)
40+
41+
_ = WalkInitializer([]string{"initializer"}, init, func(path []string, init *WorkspaceInitializer) error {
42+
git, ok := init.Spec.(*WorkspaceInitializer_Git)
43+
if !ok {
44+
return nil
45+
}
46+
47+
pwd := git.Git.Config.AuthPassword
48+
if pwd == "" || strings.HasPrefix(pwd, extractedSecretPrefix) {
49+
return nil
50+
}
51+
52+
name := strings.Join(path, ".")
53+
res[name] = pwd
54+
git.Git.Config.AuthPassword = extractedSecretPrefix + name
55+
56+
return nil
57+
})
58+
59+
return res
60+
}
61+
62+
// InjectSecretsToInitializer injects secrets to the initializer. This is the counterpart of ExtractSecretsFromInitializer.
63+
func InjectSecretsToInitializer(init *WorkspaceInitializer, secrets map[string][]byte) error {
64+
return WalkInitializer([]string{"initializer"}, init, func(path []string, init *WorkspaceInitializer) error {
65+
git, ok := init.Spec.(*WorkspaceInitializer_Git)
66+
if !ok {
67+
return nil
68+
}
69+
70+
pwd := git.Git.Config.AuthPassword
71+
if !strings.HasPrefix(pwd, extractedSecretPrefix) {
72+
return nil
73+
}
74+
75+
name := strings.TrimPrefix(pwd, extractedSecretPrefix)
76+
val, ok := secrets[name]
77+
if !ok {
78+
return xerrors.Errorf("secret %s not found", name)
79+
}
80+
81+
git.Git.Config.AuthPassword = string(val)
82+
83+
return nil
84+
})
85+
}
86+
87+
// WalkInitializer walks the initializer structure
88+
func WalkInitializer(path []string, init *WorkspaceInitializer, visitor func(path []string, init *WorkspaceInitializer) error) error {
89+
switch spec := init.Spec.(type) {
90+
case *WorkspaceInitializer_Backup:
91+
return visitor(append(path, "backup"), init)
92+
case *WorkspaceInitializer_Composite:
93+
path = append(path, "composite")
94+
err := visitor(path, init)
95+
if err != nil {
96+
return err
97+
}
98+
for i, p := range spec.Composite.Initializer {
99+
err := WalkInitializer(append(path, strconv.Itoa(i)), p, visitor)
100+
if err != nil {
101+
return err
102+
}
103+
}
104+
return nil
105+
case *WorkspaceInitializer_Download:
106+
return visitor(append(path, "download"), init)
107+
case *WorkspaceInitializer_Empty:
108+
return visitor(append(path, "empty"), init)
109+
case *WorkspaceInitializer_Git:
110+
return visitor(append(path, "git"), init)
111+
case *WorkspaceInitializer_Prebuild:
112+
child := append(path, "prebuild")
113+
err := visitor(child, init)
114+
if err != nil {
115+
return err
116+
}
117+
for i, g := range spec.Prebuild.Git {
118+
err = WalkInitializer(append(child, strconv.Itoa(i)), &WorkspaceInitializer{Spec: &WorkspaceInitializer_Git{Git: g}}, visitor)
119+
if err != nil {
120+
return err
121+
}
122+
}
123+
return nil
124+
125+
default:
126+
return fmt.Errorf("unsupported workspace initializer in walkInitializer - this is a bug in Gitpod")
127+
}
128+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package api_test
6+
7+
import (
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"github.com/gitpod-io/gitpod/content-service/api"
13+
"github.com/google/go-cmp/cmp"
14+
)
15+
16+
func TestGetCheckoutLocationsFromInitializer(t *testing.T) {
17+
var init []*api.WorkspaceInitializer
18+
init = append(init, &api.WorkspaceInitializer{
19+
Spec: &api.WorkspaceInitializer_Git{
20+
Git: &api.GitInitializer{
21+
CheckoutLocation: "/foo",
22+
CloneTaget: "head",
23+
Config: &api.GitConfig{
24+
Authentication: api.GitAuthMethod_NO_AUTH,
25+
},
26+
RemoteUri: "somewhere-else",
27+
TargetMode: api.CloneTargetMode_LOCAL_BRANCH,
28+
},
29+
},
30+
})
31+
init = append(init, &api.WorkspaceInitializer{
32+
Spec: &api.WorkspaceInitializer_Git{
33+
Git: &api.GitInitializer{
34+
CheckoutLocation: "/bar",
35+
CloneTaget: "head",
36+
Config: &api.GitConfig{
37+
Authentication: api.GitAuthMethod_NO_AUTH,
38+
},
39+
RemoteUri: "somewhere-else",
40+
TargetMode: api.CloneTargetMode_LOCAL_BRANCH,
41+
},
42+
},
43+
})
44+
45+
tests := []struct {
46+
Name string
47+
Initializer *api.WorkspaceInitializer
48+
Expectation string
49+
}{
50+
{
51+
Name: "single git initializer",
52+
Initializer: &api.WorkspaceInitializer{
53+
Spec: &api.WorkspaceInitializer_Git{
54+
Git: &api.GitInitializer{
55+
CheckoutLocation: "/foo",
56+
CloneTaget: "head",
57+
Config: &api.GitConfig{
58+
Authentication: api.GitAuthMethod_NO_AUTH,
59+
},
60+
RemoteUri: "somewhere-else",
61+
TargetMode: api.CloneTargetMode_LOCAL_BRANCH,
62+
},
63+
},
64+
},
65+
Expectation: "/foo",
66+
},
67+
{
68+
Name: "multiple git initializer",
69+
Initializer: &api.WorkspaceInitializer{
70+
Spec: &api.WorkspaceInitializer_Composite{
71+
Composite: &api.CompositeInitializer{
72+
Initializer: init,
73+
},
74+
},
75+
},
76+
Expectation: "/foo,/bar",
77+
},
78+
{
79+
Name: "backup initializer",
80+
Initializer: &api.WorkspaceInitializer{
81+
Spec: &api.WorkspaceInitializer_Backup{
82+
Backup: &api.FromBackupInitializer{
83+
CheckoutLocation: "/foobar",
84+
},
85+
},
86+
},
87+
Expectation: "/foobar",
88+
},
89+
{
90+
Name: "prebuild initializer",
91+
Initializer: &api.WorkspaceInitializer{
92+
Spec: &api.WorkspaceInitializer_Prebuild{
93+
Prebuild: &api.PrebuildInitializer{
94+
Git: []*api.GitInitializer{
95+
{CheckoutLocation: "/foo"},
96+
{CheckoutLocation: "/bar"},
97+
},
98+
},
99+
},
100+
},
101+
Expectation: "/foo,/bar",
102+
},
103+
}
104+
105+
for _, test := range tests {
106+
t.Run(test.Name, func(t *testing.T) {
107+
locations := strings.Join(api.GetCheckoutLocationsFromInitializer(test.Initializer), ",")
108+
if locations != test.Expectation {
109+
t.Errorf("expected %s, got %s", test.Expectation, locations)
110+
}
111+
})
112+
}
113+
}
114+
115+
func TestExtractInjectSecretsFromInitializer(t *testing.T) {
116+
tests := []struct {
117+
Name string
118+
Input *api.WorkspaceInitializer
119+
Expectation map[string]string
120+
}{
121+
{
122+
Name: "git initializer",
123+
Input: &api.WorkspaceInitializer{
124+
Spec: &api.WorkspaceInitializer_Git{
125+
Git: &api.GitInitializer{
126+
Config: &api.GitConfig{
127+
AuthPassword: "foobar",
128+
},
129+
},
130+
},
131+
},
132+
Expectation: map[string]string{
133+
"initializer.git": "foobar",
134+
},
135+
},
136+
{
137+
Name: "no secret git initializer",
138+
Input: &api.WorkspaceInitializer{
139+
Spec: &api.WorkspaceInitializer_Git{
140+
Git: &api.GitInitializer{
141+
Config: &api.GitConfig{},
142+
},
143+
},
144+
},
145+
Expectation: map[string]string{},
146+
},
147+
{
148+
Name: "prebuild initializer",
149+
Input: &api.WorkspaceInitializer{
150+
Spec: &api.WorkspaceInitializer_Prebuild{
151+
Prebuild: &api.PrebuildInitializer{
152+
Git: []*api.GitInitializer{
153+
{
154+
Config: &api.GitConfig{
155+
AuthPassword: "foobar",
156+
},
157+
},
158+
{
159+
Config: &api.GitConfig{
160+
AuthPassword: "some value",
161+
},
162+
},
163+
},
164+
},
165+
},
166+
},
167+
Expectation: map[string]string{
168+
"initializer.prebuild.0.git": "foobar",
169+
"initializer.prebuild.1.git": "some value",
170+
},
171+
},
172+
}
173+
174+
for _, test := range tests {
175+
t.Run(test.Name, func(t *testing.T) {
176+
act := api.ExtractSecretsFromInitializer(test.Input)
177+
if diff := cmp.Diff(test.Expectation, act); diff != "" {
178+
t.Errorf("unexpected ExtractSecretsFromInitializer (-want +got):\n%s", diff)
179+
}
180+
181+
_ = api.WalkInitializer(nil, test.Input, func(path []string, init *api.WorkspaceInitializer) error {
182+
git, ok := init.Spec.(*api.WorkspaceInitializer_Git)
183+
if !ok {
184+
return nil
185+
}
186+
if pwd := git.Git.Config.AuthPassword; pwd != "" && !strings.HasPrefix(pwd, "extracted-secret/") {
187+
t.Errorf("expected authPassword to be extracted, but got %s at %s", pwd, filepath.Join(path...))
188+
}
189+
190+
return nil
191+
})
192+
193+
injection := make(map[string][]byte, len(act))
194+
for k, v := range act {
195+
injection[k] = []byte(v)
196+
}
197+
198+
err := api.InjectSecretsToInitializer(test.Input, injection)
199+
if err != nil {
200+
t.Fatal(err)
201+
}
202+
203+
_ = api.WalkInitializer(nil, test.Input, func(path []string, init *api.WorkspaceInitializer) error {
204+
git, ok := init.Spec.(*api.WorkspaceInitializer_Git)
205+
if !ok {
206+
return nil
207+
}
208+
if pwd := git.Git.Config.AuthPassword; pwd != "" && strings.HasPrefix(pwd, "extracted-secret/") {
209+
t.Errorf("expected authPassword to be injected, but got %s at %s", pwd, filepath.Join(path...))
210+
}
211+
212+
return nil
213+
})
214+
})
215+
}
216+
}

0 commit comments

Comments
 (0)