Skip to content

Commit f1bc89c

Browse files
csweichelroboquat
authored andcommitted
[content-service] Add extract/inject secrets from/to initializer
1 parent 0f3d1d3 commit f1bc89c

File tree

2 files changed

+236
-19
lines changed

2 files changed

+236
-19
lines changed

components/content-service/pkg/initializer/initializer.go

Lines changed: 112 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net/http"
1313
"os"
1414
"path/filepath"
15+
"strconv"
1516
"strings"
1617
"time"
1718

@@ -525,24 +526,117 @@ func PlaceWorkspaceReadyFile(ctx context.Context, wspath string, initsrc csapi.W
525526
return nil
526527
}
527528

529+
// GetCheckoutLocationsFromInitializer returns a list of all checkout locations from the initializer
528530
func GetCheckoutLocationsFromInitializer(init *csapi.WorkspaceInitializer) []string {
529-
switch {
530-
case init.GetGit() != nil:
531-
return []string{init.GetGit().CheckoutLocation}
532-
case init.GetPrebuild() != nil && len(init.GetPrebuild().Git) > 0:
533-
var result = make([]string, len(init.GetPrebuild().Git))
534-
for i, c := range init.GetPrebuild().Git {
535-
result[i] = c.CheckoutLocation
536-
}
537-
return result
538-
case init.GetBackup() != nil:
539-
return []string{init.GetBackup().CheckoutLocation}
540-
case init.GetComposite() != nil:
541-
var result []string
542-
for _, c := range init.GetComposite().Initializer {
543-
result = append(result, GetCheckoutLocationsFromInitializer(c)...)
544-
}
545-
return result
531+
var res []string
532+
_ = WalkInitializer(nil, init, func(path []string, init *csapi.WorkspaceInitializer) error {
533+
switch spec := init.Spec.(type) {
534+
case *csapi.WorkspaceInitializer_Git:
535+
res = append(res, spec.Git.CheckoutLocation)
536+
case *csapi.WorkspaceInitializer_Backup:
537+
res = append(res, spec.Backup.CheckoutLocation)
538+
539+
case *csapi.WorkspaceInitializer_Prebuild:
540+
// walkInitializer will visit the Git initializer
541+
}
542+
543+
return nil
544+
})
545+
return res
546+
}
547+
548+
const extractedSecretPrefix = "extracted-secret/"
549+
550+
// ExtractSecretsFromInitializer removes secrets to the initializer.
551+
// This is the counterpart of InjectSecretsToInitializer.
552+
func ExtractSecretsFromInitializer(init *csapi.WorkspaceInitializer) map[string]string {
553+
res := make(map[string]string)
554+
555+
_ = WalkInitializer([]string{"initializer"}, init, func(path []string, init *csapi.WorkspaceInitializer) error {
556+
git, ok := init.Spec.(*csapi.WorkspaceInitializer_Git)
557+
if !ok {
558+
return nil
559+
}
560+
561+
pwd := git.Git.Config.AuthPassword
562+
if pwd == "" || strings.HasPrefix(pwd, extractedSecretPrefix) {
563+
return nil
564+
}
565+
566+
name := filepath.Join(path...)
567+
res[name] = pwd
568+
git.Git.Config.AuthPassword = extractedSecretPrefix + name
569+
570+
return nil
571+
})
572+
573+
return res
574+
}
575+
576+
// InjectSecretsToInitializer injects secrets to the initializer. This is the counterpart of ExtractSecretsFromInitializer.
577+
func InjectSecretsToInitializer(init *csapi.WorkspaceInitializer, secrets map[string]string) error {
578+
return WalkInitializer([]string{"initializer"}, init, func(path []string, init *csapi.WorkspaceInitializer) error {
579+
git, ok := init.Spec.(*csapi.WorkspaceInitializer_Git)
580+
if !ok {
581+
return nil
582+
}
583+
584+
pwd := git.Git.Config.AuthPassword
585+
if !strings.HasPrefix(pwd, extractedSecretPrefix) {
586+
return nil
587+
}
588+
589+
name := strings.TrimPrefix(pwd, extractedSecretPrefix)
590+
val, ok := secrets[name]
591+
if !ok {
592+
return xerrors.Errorf("secret %s not found", name)
593+
}
594+
595+
git.Git.Config.AuthPassword = val
596+
597+
return nil
598+
})
599+
}
600+
601+
// WalkInitializer walks the initializer structure
602+
func WalkInitializer(path []string, init *csapi.WorkspaceInitializer, visitor func(path []string, init *csapi.WorkspaceInitializer) error) error {
603+
switch spec := init.Spec.(type) {
604+
case *csapi.WorkspaceInitializer_Backup:
605+
return visitor(append(path, "backup"), init)
606+
case *csapi.WorkspaceInitializer_Composite:
607+
path = append(path, "composite")
608+
err := visitor(path, init)
609+
if err != nil {
610+
return err
611+
}
612+
for i, p := range spec.Composite.Initializer {
613+
err := WalkInitializer(append(path, strconv.Itoa(i)), p, visitor)
614+
if err != nil {
615+
return err
616+
}
617+
}
618+
return nil
619+
case *csapi.WorkspaceInitializer_Download:
620+
return visitor(append(path, "download"), init)
621+
case *csapi.WorkspaceInitializer_Empty:
622+
return visitor(append(path, "empty"), init)
623+
case *csapi.WorkspaceInitializer_Git:
624+
return visitor(append(path, "git"), init)
625+
case *csapi.WorkspaceInitializer_Prebuild:
626+
child := append(path, "prebuild")
627+
err := visitor(child, init)
628+
if err != nil {
629+
return err
630+
}
631+
for i, g := range spec.Prebuild.Git {
632+
err = WalkInitializer(append(child, strconv.Itoa(i)), &csapi.WorkspaceInitializer{Spec: &csapi.WorkspaceInitializer_Git{Git: g}}, visitor)
633+
if err != nil {
634+
return err
635+
}
636+
}
637+
return nil
638+
639+
default:
640+
return fmt.Errorf("unsupported workspace initializer in walkInitializer - this is a bug in Gitpod")
546641
}
547-
return nil
548642
}

components/content-service/pkg/initializer/initializer_test.go

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ package initializer_test
77
import (
88
"context"
99
"fmt"
10+
"path/filepath"
1011
"strings"
1112
"testing"
1213

1314
csapi "github.com/gitpod-io/gitpod/content-service/api"
1415
"github.com/gitpod-io/gitpod/content-service/pkg/archive"
1516
"github.com/gitpod-io/gitpod/content-service/pkg/initializer"
17+
"github.com/google/go-cmp/cmp"
1618
)
1719

1820
type InitializerFunc func(ctx context.Context, mappings []archive.IDMapping) (csapi.WorkspaceInitSource, error)
@@ -31,7 +33,6 @@ func (f *RecordingInitializer) Run(ctx context.Context, mappings []archive.IDMap
3133
}
3234

3335
func TestGetCheckoutLocationsFromInitializer(t *testing.T) {
34-
3536
var init []*csapi.WorkspaceInitializer
3637
init = append(init, &csapi.WorkspaceInitializer{
3738
Spec: &csapi.WorkspaceInitializer_Git{
@@ -93,6 +94,31 @@ func TestGetCheckoutLocationsFromInitializer(t *testing.T) {
9394
},
9495
Expectation: "/foo,/bar",
9596
},
97+
{
98+
Name: "backup initializer",
99+
Initializer: &csapi.WorkspaceInitializer{
100+
Spec: &csapi.WorkspaceInitializer_Backup{
101+
Backup: &csapi.FromBackupInitializer{
102+
CheckoutLocation: "/foobar",
103+
},
104+
},
105+
},
106+
Expectation: "/foobar",
107+
},
108+
{
109+
Name: "prebuild initializer",
110+
Initializer: &csapi.WorkspaceInitializer{
111+
Spec: &csapi.WorkspaceInitializer_Prebuild{
112+
Prebuild: &csapi.PrebuildInitializer{
113+
Git: []*csapi.GitInitializer{
114+
{CheckoutLocation: "/foo"},
115+
{CheckoutLocation: "/bar"},
116+
},
117+
},
118+
},
119+
},
120+
Expectation: "/foo,/bar",
121+
},
96122
}
97123

98124
for _, test := range tests {
@@ -103,7 +129,104 @@ func TestGetCheckoutLocationsFromInitializer(t *testing.T) {
103129
}
104130
})
105131
}
132+
}
133+
134+
func TestExtractInjectSecretsFromInitializer(t *testing.T) {
135+
tests := []struct {
136+
Name string
137+
Input *csapi.WorkspaceInitializer
138+
Expectation map[string]string
139+
}{
140+
{
141+
Name: "git initializer",
142+
Input: &csapi.WorkspaceInitializer{
143+
Spec: &csapi.WorkspaceInitializer_Git{
144+
Git: &csapi.GitInitializer{
145+
Config: &csapi.GitConfig{
146+
AuthPassword: "foobar",
147+
},
148+
},
149+
},
150+
},
151+
Expectation: map[string]string{
152+
"initializer/git": "foobar",
153+
},
154+
},
155+
{
156+
Name: "no secret git initializer",
157+
Input: &csapi.WorkspaceInitializer{
158+
Spec: &csapi.WorkspaceInitializer_Git{
159+
Git: &csapi.GitInitializer{
160+
Config: &csapi.GitConfig{},
161+
},
162+
},
163+
},
164+
Expectation: map[string]string{},
165+
},
166+
{
167+
Name: "prebuild initializer",
168+
Input: &csapi.WorkspaceInitializer{
169+
Spec: &csapi.WorkspaceInitializer_Prebuild{
170+
Prebuild: &csapi.PrebuildInitializer{
171+
Git: []*csapi.GitInitializer{
172+
{
173+
Config: &csapi.GitConfig{
174+
AuthPassword: "foobar",
175+
},
176+
},
177+
{
178+
Config: &csapi.GitConfig{
179+
AuthPassword: "some value",
180+
},
181+
},
182+
},
183+
},
184+
},
185+
},
186+
Expectation: map[string]string{
187+
"initializer/prebuild/0/git": "foobar",
188+
"initializer/prebuild/1/git": "some value",
189+
},
190+
},
191+
}
106192

193+
for _, test := range tests {
194+
t.Run(test.Name, func(t *testing.T) {
195+
act := initializer.ExtractSecretsFromInitializer(test.Input)
196+
if diff := cmp.Diff(test.Expectation, act); diff != "" {
197+
t.Errorf("unexpected ExtractSecretsFromInitializer (-want +got):\n%s", diff)
198+
}
199+
200+
_ = initializer.WalkInitializer(nil, test.Input, func(path []string, init *csapi.WorkspaceInitializer) error {
201+
git, ok := init.Spec.(*csapi.WorkspaceInitializer_Git)
202+
if !ok {
203+
return nil
204+
}
205+
if pwd := git.Git.Config.AuthPassword; pwd != "" && !strings.HasPrefix(pwd, "extracted-secret/") {
206+
t.Errorf("expected authPassword to be extracted, but got %s at %s", pwd, filepath.Join(path...))
207+
}
208+
209+
return nil
210+
})
211+
212+
err := initializer.InjectSecretsToInitializer(test.Input, act)
213+
if err != nil {
214+
t.Fatal(err)
215+
}
216+
217+
_ = initializer.WalkInitializer(nil, test.Input, func(path []string, init *csapi.WorkspaceInitializer) error {
218+
git, ok := init.Spec.(*csapi.WorkspaceInitializer_Git)
219+
if !ok {
220+
return nil
221+
}
222+
if pwd := git.Git.Config.AuthPassword; pwd != "" && strings.HasPrefix(pwd, "extracted-secret/") {
223+
t.Errorf("expected authPassword to be injected, but got %s at %s", pwd, filepath.Join(path...))
224+
}
225+
226+
return nil
227+
})
228+
})
229+
}
107230
}
108231

109232
func TestCompositeInitializer(t *testing.T) {

0 commit comments

Comments
 (0)