Skip to content

Commit 829ba3e

Browse files
jimmidysondkoshkin
andauthored
fix: correctly handle multiple registries with the same Host [Backport v0.27.x] (#1064)
**What problem does this PR solve?**: Backport of #1063 **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. --> **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: Dimitri Koshkin <[email protected]>
1 parent 213d6aa commit 829ba3e

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)