Skip to content

Commit e88d493

Browse files
dkoshkinjimmidyson
andauthored
fix: correctly handle multiple registries with the same Host (#1063)
**What problem does this PR solve?**: Write out a single CA file for multiple registries with the same url.Host. Return an error if the CA content for those registries do not match. This fix is needed after another recent fix 17bc80d#diff-3b57926153158559026700fdb9392d259a62201d7eb485f3d0d62fdeaa976ffdL57-L64 that now correctly generates the CA files without the url.Path. **Which issue(s) this PR fixes**: Fixes # **How Has This Been Tested?**: <!-- Please describe the tests that you ran to verify your changes. Provide output from the tests and any manual steps needed to replicate the tests. --> New unit tests. **Special notes for your reviewer**: <!-- Use this to provide any additional information to the reviewers. This may include: - Best way to review the PR. - Where the author wants the most review attention on. - etc. --> --------- Co-authored-by: Jimmi Dyson <[email protected]>
1 parent dbaf14b commit e88d493

File tree

2 files changed

+142
-18
lines changed

2 files changed

+142
-18
lines changed

pkg/handlers/generic/mutation/mirrors/containerd_files.go

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ package mirrors
66
import (
77
"bytes"
88
_ "embed"
9+
"errors"
910
"fmt"
1011
"net/url"
1112
"path"
1213
"strings"
1314
"text/template"
1415

16+
"github.com/samber/lo"
1517
cabpkv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
1618

1719
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/common"
@@ -139,21 +141,17 @@ func generateRegistryCACertFiles(
139141

140142
var files []cabpkv1.File //nolint:prealloc // We don't know the size of the slice yet.
141143

142-
for _, config := range configs {
143-
if config.CASecretName == "" {
144-
continue
145-
}
146-
147-
registryCACertPathOnRemote, err := config.filePathFromURL()
148-
if err != nil {
149-
return nil, fmt.Errorf("failed generating CA certificate file path from URL: %w", err)
150-
}
144+
filesToGenerate, err := registryCACertFiles(configs)
145+
if err != nil {
146+
return nil, err
147+
}
148+
for _, file := range filesToGenerate {
151149
files = append(files, cabpkv1.File{
152-
Path: registryCACertPathOnRemote,
150+
Path: file.path,
153151
Permissions: "0600",
154152
ContentFrom: &cabpkv1.FileSource{
155153
Secret: cabpkv1.SecretFileSource{
156-
Name: config.CASecretName,
154+
Name: file.caSecretName,
157155
Key: secretKeyForCACert,
158156
},
159157
},
@@ -189,3 +187,65 @@ func formatURLForContainerd(uri string) (string, error) {
189187
// using path.Join on all elements incorrectly drops a "/" from "https://"
190188
return fmt.Sprintf("%s/%s", mirror, mirrorPath), nil
191189
}
190+
191+
type containerdConfigFile struct {
192+
path string
193+
url string
194+
caSecretName string
195+
caCert string
196+
}
197+
198+
var ErrConflictingRegistryCACertificates = errors.New(
199+
"conflicting CA certificate specified for registry host",
200+
)
201+
202+
// registryCACertFiles returns a list of CA certificate files
203+
// that should be generated for the given containerd configurations.
204+
// If any of the provided configurations share the same url.Host only a single file will be generated.
205+
// An error will be returned, if the CA certificate content for the same URL.Host do not match.
206+
func registryCACertFiles(configs []containerdConfig) ([]containerdConfigFile, error) {
207+
filesToGenerate := make([]containerdConfigFile, 0, len(configs))
208+
209+
for _, config := range configs {
210+
// Skip if CA certificate is not provided.
211+
if config.CASecretName == "" {
212+
continue
213+
}
214+
registryCACertPathOnRemote, err := config.filePathFromURL()
215+
if err != nil {
216+
return nil, fmt.Errorf("failed generating CA certificate file path from URL: %w", err)
217+
}
218+
219+
existingFileToGenerate, existing := lo.Find(
220+
filesToGenerate,
221+
func(f containerdConfigFile) bool {
222+
return registryCACertPathOnRemote == f.path
223+
},
224+
)
225+
// File exists and needs to be checked for conflicts.
226+
if existing {
227+
if config.CACert != existingFileToGenerate.caCert {
228+
return nil, fmt.Errorf(
229+
"%w: %q (from secrets %q and %q)",
230+
ErrConflictingRegistryCACertificates,
231+
config.URL,
232+
config.CASecretName,
233+
existingFileToGenerate.caSecretName,
234+
)
235+
}
236+
237+
// File already found with matching content - skipping.
238+
continue
239+
}
240+
241+
// File not already found and needs to be generated.
242+
filesToGenerate = append(filesToGenerate, containerdConfigFile{
243+
path: registryCACertPathOnRemote,
244+
url: config.URL,
245+
caSecretName: config.CASecretName,
246+
caCert: config.CACert,
247+
})
248+
}
249+
250+
return filesToGenerate, nil
251+
}

pkg/handlers/generic/mutation/mirrors/containerd_files_test.go

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ func Test_generateContainerdDefaultHostsFile(t *testing.T) {
4040
override_path = true
4141
`,
4242
},
43-
wantErr: nil,
4443
},
4544
{
4645
name: "ECR mirror image registry with a path and no CA certificate",
@@ -63,7 +62,6 @@ func Test_generateContainerdDefaultHostsFile(t *testing.T) {
6362
override_path = true
6463
`,
6564
},
66-
wantErr: nil,
6765
},
6866
{
6967
name: "Mirror image registry with a CA and an image registry with no CA certificate",
@@ -91,7 +89,6 @@ func Test_generateContainerdDefaultHostsFile(t *testing.T) {
9189
override_path = true
9290
`,
9391
},
94-
wantErr: nil,
9592
},
9693
{
9794
name: "Mirror image registry with a CA and an image registry with a CA",
@@ -124,7 +121,6 @@ func Test_generateContainerdDefaultHostsFile(t *testing.T) {
124121
override_path = true
125122
`,
126123
},
127-
wantErr: nil,
128124
},
129125
{
130126
name: "Image registry with a CA",
@@ -135,8 +131,6 @@ func Test_generateContainerdDefaultHostsFile(t *testing.T) {
135131
},
136132
},
137133
want: nil,
138-
139-
wantErr: nil,
140134
},
141135
}
142136
for idx := range tests {
@@ -156,6 +150,7 @@ func Test_generateRegistryCACertFiles(t *testing.T) {
156150
name string
157151
configs []containerdConfig
158152
want []cabpkv1.File
153+
wantErr error
159154
}{
160155
{
161156
name: "ECR mirror image registry with no CA certificate",
@@ -173,8 +168,41 @@ func Test_generateRegistryCACertFiles(t *testing.T) {
173168
{
174169
URL: "https://registry.example.com",
175170
CASecretName: "my-registry-credentials-secret",
171+
CACert: "-----BEGIN CERTIFICATE-----",
172+
Mirror: true,
173+
},
174+
},
175+
want: []cabpkv1.File{
176+
{
177+
Path: "/etc/containerd/certs.d/registry.example.com/ca.crt",
178+
Owner: "",
179+
Permissions: "0600",
180+
Encoding: "",
181+
Append: false,
182+
ContentFrom: &cabpkv1.FileSource{
183+
Secret: cabpkv1.SecretFileSource{
184+
Name: "my-registry-credentials-secret",
185+
Key: "ca.crt",
186+
},
187+
},
188+
},
189+
},
190+
},
191+
{
192+
name: "Mirror image registry and registry with the same CA certificate",
193+
configs: []containerdConfig{
194+
{
195+
URL: "https://registry.example.com/mirror",
196+
CASecretName: "my-registry-credentials-secret",
197+
CACert: "-----BEGIN CERTIFICATE-----",
176198
Mirror: true,
177199
},
200+
{
201+
URL: "https://registry.example.com/library",
202+
CASecretName: "my-registry-credentials-secret",
203+
CACert: "-----BEGIN CERTIFICATE-----",
204+
Mirror: false,
205+
},
178206
},
179207
want: []cabpkv1.File{
180208
{
@@ -192,13 +220,49 @@ func Test_generateRegistryCACertFiles(t *testing.T) {
192220
},
193221
},
194222
},
223+
{
224+
name: "Mirror image registry and registry with different CA certificates",
225+
configs: []containerdConfig{
226+
{
227+
URL: "https://registry.example.com/mirror",
228+
CASecretName: "my-registry-credentials-secret",
229+
CACert: "-----BEGIN CERTIFICATE-----",
230+
Mirror: true,
231+
},
232+
{
233+
URL: "https://registry.example.com/library",
234+
CASecretName: "my-registry-credentials-secret",
235+
CACert: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----",
236+
Mirror: false,
237+
},
238+
},
239+
wantErr: ErrConflictingRegistryCACertificates,
240+
},
241+
{
242+
name: "Multiple image registries with different CA certificates",
243+
configs: []containerdConfig{
244+
{
245+
URL: "https://registry.example.com/mirror",
246+
CASecretName: "my-registry-credentials-secret",
247+
CACert: "-----BEGIN CERTIFICATE-----",
248+
Mirror: false,
249+
},
250+
{
251+
URL: "https://registry.example.com/library",
252+
CASecretName: "my-registry-credentials-secret",
253+
CACert: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----",
254+
Mirror: false,
255+
},
256+
},
257+
wantErr: ErrConflictingRegistryCACertificates,
258+
},
195259
}
196260
for idx := range tests {
197261
tt := tests[idx]
198262
t.Run(tt.name, func(t *testing.T) {
199263
t.Parallel()
200264
file, err := generateRegistryCACertFiles(tt.configs)
201-
require.NoError(t, err)
265+
require.ErrorIs(t, err, tt.wantErr)
202266
assert.Equal(t, tt.want, file)
203267
})
204268
}

0 commit comments

Comments
 (0)