Skip to content

Commit 3270183

Browse files
author
Paulo Gomes
committed
Add validation to TargetPath and ValuesKey
ValuesKey must be a valid Data Key, therefore the same logic used by upstream Kubernetes is reused here to ensure a valid key is being used. For TargetPath a loose regex is being used and a max length is enforced. The validation is only checked within the reconciliation, to avoid causing issues to users upgrading into a new version and have HelmReleases objects in an existing cluster, that would no longer be accepted by the API server. Signed-off-by: Paulo Gomes <paulo.gomes@weave.works>
1 parent 2195310 commit 3270183

File tree

5 files changed

+102
-1
lines changed

5 files changed

+102
-1
lines changed

api/v2beta1/reference_types.go

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ type ValuesReference struct {
5959

6060
// ValuesKey is the data key where the values.yaml or a specific value can be
6161
// found at. Defaults to 'values.yaml'.
62+
// When set, must be a valid Data Key, consisting of alphanumeric characters,
63+
// '-', '_' or '.'.
6264
// +optional
6365
ValuesKey string `json:"valuesKey,omitempty"`
6466

config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,8 @@ spec:
701701
valuesKey:
702702
description: ValuesKey is the data key where the values.yaml
703703
or a specific value can be found at. Defaults to 'values.yaml'.
704+
When set, must be a valid Data Key, consisting of alphanumeric
705+
characters, '-', '_' or '.'.
704706
type: string
705707
required:
706708
- kind

controllers/helmrelease_controller.go

+33
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23+
"regexp"
2324
"strings"
2425
"time"
2526

@@ -34,6 +35,7 @@ import (
3435
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3536
"k8s.io/apimachinery/pkg/runtime"
3637
"k8s.io/apimachinery/pkg/types"
38+
"k8s.io/apimachinery/pkg/util/validation"
3739
"k8s.io/cli-runtime/pkg/genericclioptions"
3840
"k8s.io/client-go/rest"
3941
kuberecorder "k8s.io/client-go/tools/record"
@@ -65,6 +67,11 @@ import (
6567
"github.com/fluxcd/helm-controller/internal/util"
6668
)
6769

70+
var (
71+
targetPathMaxLen = 250
72+
targetPathRegex = regexp.MustCompilePOSIX(`^([a-zA-Z0-9_.]|\[[0-9]{1,5}\])+$`)
73+
)
74+
6875
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
6976
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases/status,verbs=get;update;patch
7077
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases/finalizers,verbs=get;create;update;patch;delete
@@ -512,6 +519,14 @@ func (r *HelmReleaseReconciler) composeValues(ctx context.Context, hr v2.HelmRel
512519
namespacedName := types.NamespacedName{Namespace: hr.Namespace, Name: v.Name}
513520
var valuesData []byte
514521

522+
if !isValidValuesKey(v.ValuesKey) {
523+
return nil, fmt.Errorf("invalid ValuesKey: %s", v.ValuesKey)
524+
}
525+
526+
if !isValidTargetPath(v.TargetPath) {
527+
return nil, fmt.Errorf("invalid TargetPath: %s", v.TargetPath)
528+
}
529+
515530
switch v.Kind {
516531
case "ConfigMap":
517532
resource, ok := configMaps[namespacedName.String()]
@@ -772,3 +787,21 @@ func (r *HelmReleaseReconciler) recordReadiness(ctx context.Context, hr v2.HelmR
772787
}, !hr.DeletionTimestamp.IsZero())
773788
}
774789
}
790+
791+
func isValidTargetPath(targetPath string) bool {
792+
if len(targetPath) > targetPathMaxLen {
793+
return false
794+
}
795+
if targetPath == "" {
796+
return true
797+
}
798+
return targetPathRegex.MatchString(targetPath)
799+
}
800+
801+
func isValidValuesKey(valuesKey string) bool {
802+
if valuesKey == "" {
803+
return true
804+
}
805+
// validation.IsConfigMapKey returns an array of errors found.
806+
return len(validation.IsConfigMapKey(valuesKey)) == 0
807+
}

controllers/helmrelease_controller_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controllers
1919
import (
2020
"context"
2121
"reflect"
22+
"strings"
2223
"testing"
2324

2425
"github.com/go-logr/logr"
@@ -249,6 +250,21 @@ invalid`,
249250
},
250251
wantErr: true,
251252
},
253+
{
254+
name: "invalid target path",
255+
resources: []runtime.Object{
256+
valuesSecret("values", map[string][]byte{"single": []byte("true")}),
257+
},
258+
references: []v2.ValuesReference{
259+
{
260+
Kind: "Secret",
261+
Name: "values",
262+
ValuesKey: "single",
263+
TargetPath: "a%",
264+
},
265+
},
266+
wantErr: true,
267+
},
252268
}
253269

254270
for _, tt := range tests {
@@ -278,6 +294,52 @@ invalid`,
278294
}
279295
}
280296

297+
func TestTargetPath(t *testing.T) {
298+
tests := []struct {
299+
name string
300+
input []string
301+
want bool
302+
}{
303+
{
304+
name: "valid TargetPaths",
305+
input: []string{
306+
"list2",
307+
"with_underscore",
308+
"list.example[2]",
309+
"list[2][2].test",
310+
"some.longer.multi.level.target.path.that.is.too.long.to.see.but.still.within.a.reasonable.limit",
311+
},
312+
want: true,
313+
},
314+
{
315+
name: "invalid TargetPaths",
316+
input: []string{
317+
func() string { return strings.Repeat("a", 251) }(),
318+
"test=",
319+
"test%",
320+
"test[623221].a",
321+
"test[a]",
322+
"test]0[",
323+
"test[0[",
324+
"test[0",
325+
"test$",
326+
},
327+
want: false,
328+
},
329+
}
330+
331+
for _, tt := range tests {
332+
t.Run(tt.name, func(t *testing.T) {
333+
for _, targetPath := range tt.input {
334+
got := isValidTargetPath(targetPath)
335+
if got != tt.want {
336+
t.Errorf("%s: got = %v, want %v", targetPath, got, tt.want)
337+
}
338+
}
339+
})
340+
}
341+
}
342+
281343
func valuesSecret(name string, data map[string][]byte) *corev1.Secret {
282344
return &corev1.Secret{
283345
ObjectMeta: metav1.ObjectMeta{Name: name},

docs/api/helmrelease.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -2092,7 +2092,9 @@ string
20922092
<td>
20932093
<em>(Optional)</em>
20942094
<p>ValuesKey is the data key where the values.yaml or a specific value can be
2095-
found at. Defaults to &lsquo;values.yaml&rsquo;.</p>
2095+
found at. Defaults to &lsquo;values.yaml&rsquo;.
2096+
When set, must be a valid Data Key, consisting of alphanumeric characters,
2097+
&lsquo;-&rsquo;, &lsquo;_&rsquo; or &lsquo;.&rsquo;.</p>
20962098
</td>
20972099
</tr>
20982100
<tr>

0 commit comments

Comments
 (0)