Skip to content

Commit 819a5ef

Browse files
authored
substitutes template protects against reuse of base/candidate in same template (#1942)
Signed-off-by: grokspawn <jordan@nimblewidget.com>
1 parent 80b5294 commit 819a5ef

2 files changed

Lines changed: 54 additions & 5 deletions

File tree

alpha/template/substitutes/substitutes.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,23 @@ func (t *template) Render(ctx context.Context, reader io.Reader) (*declcfg.Decla
7878
return nil, fmt.Errorf("render: entries are not valid FBC: %v", err)
7979
}
8080

81+
// track substitution base duplication
82+
seenBases := make(map[string]bool)
83+
// track substitution candidate duplication with different bases
84+
seenCandidates := make(map[string]string)
85+
8186
// Process each substitution
8287
for _, substitution := range st.Substitutions {
88+
if seenBases[substitution.Base] {
89+
return nil, fmt.Errorf("render: cannot reuse the same substitution base %q", substitution.Base)
90+
}
91+
seenBases[substitution.Base] = true
92+
93+
if candidateBase, ok := seenCandidates[substitution.Name]; ok {
94+
return nil, fmt.Errorf("render: cannot reuse the same substitution %q for different bases (%q and %q)", substitution.Name, candidateBase, substitution.Base)
95+
}
96+
seenCandidates[substitution.Name] = substitution.Base
97+
8398
err := t.processSubstitution(ctx, cfg, substitution)
8499
if err != nil {
85100
return nil, fmt.Errorf("render: error processing substitution %s->%s: %v", substitution.Base, substitution.Name, err)
@@ -89,8 +104,6 @@ func (t *template) Render(ctx context.Context, reader io.Reader) (*declcfg.Decla
89104
return cfg, nil
90105
}
91106

92-
// Helper functions
93-
94107
func parseSpec(reader io.Reader) (*SubstitutesTemplateData, error) {
95108
st := &SubstitutesTemplateData{}
96109
stDoc := json.RawMessage{}
@@ -165,7 +178,7 @@ func (t *template) validateSubstitution(ctx context.Context, cfg *declcfg.Declar
165178
return nil
166179
}
167180

168-
// processSubstitution handles the complex logic for processing a single substitution
181+
// processSubstitution processes a single substitution
169182
func (t *template) processSubstitution(ctx context.Context, cfg *declcfg.DeclarativeConfig, substitution Substitute) error {
170183
if err := t.validateSubstitution(ctx, cfg, substitution); err != nil {
171184
return err
@@ -176,8 +189,6 @@ func (t *template) processSubstitution(ctx context.Context, cfg *declcfg.Declara
176189
return fmt.Errorf("failed to render bundle image reference %q: %v", substitution.Name, err)
177190
}
178191

179-
// normally, we'd rely RenderBundle to represent any failure via err, but since this is comes from input,
180-
// we need to perform more validation of the results here before processing them
181192
if substituteCfg == nil || len(substituteCfg.Bundles) == 0 {
182193
return fmt.Errorf("rendered bundle image reference %q contains no bundles", substitution.Name)
183194
}

alpha/template/substitutes/substitutes_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,44 @@ func TestRender(t *testing.T) {
568568
expectError: true,
569569
errorContains: "substitution name and base cannot be the same",
570570
},
571+
{
572+
name: "Error/render with duplicate base in substitutions",
573+
entries: []*declcfg.Meta{
574+
createValidTestPackageMeta("testoperator", "stable"),
575+
createValidTestChannelMeta("stable", "testoperator", []declcfg.ChannelEntry{
576+
{Name: "testoperator.v1.0.0"},
577+
{Name: "testoperator.v1.1.0", Replaces: "testoperator.v1.0.0"},
578+
}),
579+
createValidTestBundleMeta("testoperator.v1.0.0", "testoperator", "1.0.0", ""),
580+
createValidTestBundleMeta("testoperator.v1.1.0", "testoperator", "1.1.0", ""),
581+
},
582+
substitutions: []Substitute{
583+
{Name: "quay.io/test/testoperator-bundle:v1.2.0-alpha", Base: "testoperator.v1.1.0"},
584+
{Name: "quay.io/test/testoperator-bundle:v1.3.0-alpha", Base: "testoperator.v1.1.0"}, // Invalid: reusing same base
585+
},
586+
expectError: true,
587+
errorContains: "cannot reuse the same substitution base",
588+
},
589+
{
590+
name: "Error/render with same substitution name for different bases",
591+
entries: []*declcfg.Meta{
592+
createValidTestPackageMeta("testoperator", "stable"),
593+
createValidTestChannelMeta("stable", "testoperator", []declcfg.ChannelEntry{
594+
{Name: "testoperator.v1.0.0"},
595+
{Name: "testoperator.v1.1.0", Replaces: "testoperator.v1.0.0"},
596+
{Name: "testoperator.v1.2.0", Replaces: "testoperator.v1.1.0"},
597+
}),
598+
createValidTestBundleMeta("testoperator.v1.0.0", "testoperator", "1.0.0", ""),
599+
createValidTestBundleMeta("testoperator.v1.1.0", "testoperator", "1.1.0", ""),
600+
createValidTestBundleMeta("testoperator.v1.2.0", "testoperator", "1.2.0", ""),
601+
},
602+
substitutions: []Substitute{
603+
{Name: "quay.io/test/testoperator-bundle:v1.3.0-alpha", Base: "testoperator.v1.1.0"},
604+
{Name: "quay.io/test/testoperator-bundle:v1.3.0-alpha", Base: "testoperator.v1.2.0"}, // Invalid: reusing same name with different base
605+
},
606+
expectError: true,
607+
errorContains: "cannot reuse the same substitution",
608+
},
571609
}
572610

573611
for _, tt := range tests {

0 commit comments

Comments
 (0)