Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.

Commit 2b52f4a

Browse files
✨ Support specifying the CA cert or adding verify=false into clouds.yaml (#148)
* Support specifying the CA cert or adding verify=false into clouds.yaml Signed-off-by: michal.gubricky <[email protected]> * Adds troubleshooting guide Signed-off-by: michal.gubricky <[email protected]> * Update docs/troubleshooting.md Co-authored-by: Roman Hros <[email protected]> Signed-off-by: Michal Gubricky <[email protected]> * Update docs/troubleshooting.md Co-authored-by: Roman Hros <[email protected]> Signed-off-by: Michal Gubricky <[email protected]> * Update docs/troubleshooting.md Co-authored-by: Roman Hros <[email protected]> Signed-off-by: Michal Gubricky <[email protected]> * Update docs/troubleshooting.md Co-authored-by: Roman Hros <[email protected]> Signed-off-by: Michal Gubricky <[email protected]> --------- Signed-off-by: michal.gubricky <[email protected]> Signed-off-by: Michal Gubricky <[email protected]> Co-authored-by: Roman Hros <[email protected]>
1 parent c5c92cc commit 2b52f4a

File tree

3 files changed

+146
-14
lines changed

3 files changed

+146
-14
lines changed

docs/troubleshooting.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Troubleshooting
2+
3+
This guide explains general info on how to debug issues if a cluster creation fails.
4+
5+
## providerClient authentication err
6+
7+
If you are using https, and when you encounter issues like:
8+
9+
```
10+
kubectl logs -n cspo-system -l control-plane=controller-manager
11+
...
12+
[manager] 2024-04-15T15:20:07Z DEBUG events Post "https://10.0.3.15/identity/v3/auth/tokens": tls: failed to verify certificate: x509: certificate signed by unknown authority {"type": "Warning", "object": {"kind":"OpenStackNodeImageRelease","namespace":"cluster","name":"openstack-ferrol-1-27-ubuntu-capi-image-v1.27.8-v2","uid":"93d2c1c8-5a19-45f8-9f93-8e8bd5227ebf","apiVersion":"infrastructure.clusterstack.x-k8s.io/v1alpha1","resourceVersion":"3773"}, "reason": "OpenStackProviderClientNotSet"}
13+
...
14+
```
15+
16+
you must specify the CA certificate in your secret, which contains the access data to the OpenStack instance, then secret should look similar to this example:
17+
18+
```bash
19+
apiVersion: v1
20+
data:
21+
cacert: <PEM_ENCODED_CA_CERT>
22+
clouds.yaml: <ENCODED_CLOUDS_YAML>
23+
kind: Secret
24+
metadata:
25+
labels:
26+
clusterctl.cluster.x-k8s.io/move: "true"
27+
name: "openstack"
28+
namespace: cluster
29+
```

internal/controller/openstacknodeimagerelease_controller.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ package controller
1919

2020
import (
2121
"context"
22+
"crypto/tls"
23+
"crypto/x509"
2224
"fmt"
25+
"net/http"
2326
"time"
2427

2528
"github.com/gophercloud/gophercloud/v2"
@@ -53,6 +56,7 @@ const (
5356
defaultCloudName = "openstack"
5457
cloudNameSecretKey = "cloudName"
5558
cloudsSecretKey = "clouds.yaml"
59+
caSecretKey = "cacert"
5660
waitForImageBecomeActive = 30 * time.Second
5761
)
5862

@@ -97,7 +101,7 @@ func (r *OpenStackNodeImageReleaseReconciler) Reconcile(ctx context.Context, req
97101
}()
98102

99103
// Get OpenStack cloud config from sercet
100-
cloud, err := r.getCloudFromSecret(ctx, openstacknodeimagerelease.Namespace, openstacknodeimagerelease.Spec.IdentityRef.Name)
104+
cloud, caCert, err := r.getCloudFromSecret(ctx, openstacknodeimagerelease.Namespace, openstacknodeimagerelease.Spec.IdentityRef.Name)
101105
if err != nil {
102106
if apierrors.IsNotFound(err) {
103107
conditions.MarkFalse(openstacknodeimagerelease,
@@ -122,9 +126,39 @@ func (r *OpenStackNodeImageReleaseReconciler) Reconcile(ctx context.Context, req
122126

123127
conditions.MarkTrue(openstacknodeimagerelease, apiv1alpha1.CloudAvailableCondition)
124128

129+
clientOpts := new(clientconfig.ClientOpts)
130+
131+
if cloud.AuthInfo != nil {
132+
clientOpts.AuthInfo = cloud.AuthInfo
133+
clientOpts.AuthType = cloud.AuthType
134+
clientOpts.RegionName = cloud.RegionName
135+
clientOpts.EndpointType = cloud.EndpointType
136+
}
137+
opts, _ := clientconfig.AuthOptions(clientOpts)
138+
opts.AllowReauth = true
139+
125140
// Create an OpenStack provider client
126-
opts := &clientconfig.ClientOpts{AuthInfo: cloud.AuthInfo}
127-
providerClient, err := clientconfig.AuthenticatedClient(ctx, opts)
141+
providerClient, _ := openstack.NewClient(opts.IdentityEndpoint)
142+
143+
config := &tls.Config{
144+
MinVersion: tls.VersionTLS12,
145+
}
146+
147+
if cloud.Verify != nil {
148+
config.InsecureSkipVerify = !*cloud.Verify
149+
}
150+
151+
if caCert != nil {
152+
config.RootCAs = x509.NewCertPool()
153+
ok := config.RootCAs.AppendCertsFromPEM(caCert)
154+
if !ok {
155+
// If no certificates were successfully parsed, set RootCAs to nil
156+
config.RootCAs = nil
157+
}
158+
}
159+
160+
providerClient.HTTPClient.Transport = &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config}
161+
err = openstack.Authenticate(ctx, providerClient, *opts)
128162
if err != nil {
129163
record.Warnf(openstacknodeimagerelease, "OpenStackProviderClientNotSet", err.Error())
130164
logger.Error(err, "failed to create a provider client")
@@ -293,7 +327,7 @@ func (r *OpenStackNodeImageReleaseReconciler) Reconcile(ctx context.Context, req
293327
return ctrl.Result{}, nil
294328
}
295329

296-
func (r *OpenStackNodeImageReleaseReconciler) getCloudFromSecret(ctx context.Context, secretNamespace, secretName string) (clientconfig.Cloud, error) {
330+
func (r *OpenStackNodeImageReleaseReconciler) getCloudFromSecret(ctx context.Context, secretNamespace, secretName string) (clientconfig.Cloud, []byte, error) {
297331
var clouds clientconfig.Clouds
298332
emptyCloud := clientconfig.Cloud{}
299333
var cloudName string
@@ -304,31 +338,38 @@ func (r *OpenStackNodeImageReleaseReconciler) getCloudFromSecret(ctx context.Con
304338
Name: secretName,
305339
}, secret)
306340
if err != nil {
307-
return emptyCloud, fmt.Errorf("failed to get secret %s in namespace %s: %w", secretName, secretNamespace, err)
341+
return emptyCloud, nil, fmt.Errorf("failed to get secret %s in namespace %s: %w", secretName, secretNamespace, err)
308342
}
309343

310344
content, ok := secret.Data[cloudNameSecretKey]
311345
if !ok {
312346
cloudName = defaultCloudName
313347
} else {
314348
if err := yaml.Unmarshal(content, &cloudName); err != nil {
315-
return emptyCloud, fmt.Errorf("failed to unmarshal cloudName stored in secret %s: %w", secretName, err)
349+
return emptyCloud, nil, fmt.Errorf("failed to unmarshal cloudName stored in secret %s: %w", secretName, err)
316350
}
317351
}
318352

319353
content, ok = secret.Data[cloudsSecretKey]
320354
if !ok {
321-
return emptyCloud, fmt.Errorf("OpenStack credentials secret %s did not contain key %s", secretName, cloudsSecretKey)
355+
return emptyCloud, nil, fmt.Errorf("OpenStack credentials secret %s did not contain key %s", secretName, cloudsSecretKey)
322356
}
323357
if err = yaml.Unmarshal(content, &clouds); err != nil {
324-
return emptyCloud, fmt.Errorf("failed to unmarshal clouds credentials stored in secret %s: %w", secretName, err)
358+
return emptyCloud, nil, fmt.Errorf("failed to unmarshal clouds credentials stored in secret %s: %w", secretName, err)
325359
}
326360

327361
cloud, ok := clouds.Clouds[cloudName]
328362
if !ok {
329-
return emptyCloud, fmt.Errorf("failed to find cloud %s in %s", cloudName, cloudsSecretKey)
363+
return emptyCloud, nil, fmt.Errorf("failed to find cloud %s in %s", cloudName, cloudsSecretKey)
330364
}
331-
return cloud, nil
365+
366+
// get caCert
367+
caCert, ok := secret.Data[caSecretKey]
368+
if !ok {
369+
return cloud, nil, nil
370+
}
371+
372+
return cloud, caCert, nil
332373
}
333374

334375
func getImageID(ctx context.Context, imagesClient *gophercloud.ServiceClient, imageCreateOps *apiv1alpha1.CreateOpts) (string, error) {

internal/controller/openstacknodeimagerelease_controller_test.go

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ clouds:
6868
Client: client,
6969
}
7070

71-
cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
71+
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
7272

7373
expectedCloud := clientconfig.Cloud{
7474
AuthInfo: &clientconfig.AuthInfo{
@@ -83,6 +83,65 @@ clouds:
8383
}
8484
assert.NoError(t, err)
8585
assert.Equal(t, expectedCloud, cloud)
86+
assert.Equal(t, []byte(nil), caCert)
87+
88+
err = client.Delete(context.TODO(), secret)
89+
assert.NoError(t, err)
90+
}
91+
92+
func TestGetCloudFromSecretWithCaCert(t *testing.T) {
93+
client := fake.NewClientBuilder().Build()
94+
95+
secretName := "test-secret"
96+
secretNamespace := "test-namespace"
97+
cloudsYAML := `
98+
clouds:
99+
openstack:
100+
auth:
101+
username: test_user
102+
password: test_password
103+
project_name: test_project
104+
project_id: test_project_id
105+
auth_url: test_auth_url
106+
domain_name: test_domain
107+
region_name: test_region
108+
`
109+
expectedcaCert := []byte("test-ca-cert")
110+
111+
secret := &corev1.Secret{
112+
ObjectMeta: metav1.ObjectMeta{
113+
Name: secretName,
114+
Namespace: secretNamespace,
115+
},
116+
Data: map[string][]byte{
117+
cloudsSecretKey: []byte(cloudsYAML),
118+
caSecretKey: expectedcaCert,
119+
},
120+
Type: corev1.SecretTypeOpaque,
121+
}
122+
err := client.Create(context.TODO(), secret)
123+
assert.NoError(t, err)
124+
125+
r := &OpenStackNodeImageReleaseReconciler{
126+
Client: client,
127+
}
128+
129+
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
130+
131+
expectedCloud := clientconfig.Cloud{
132+
AuthInfo: &clientconfig.AuthInfo{
133+
Username: "test_user",
134+
Password: "test_password",
135+
ProjectName: "test_project",
136+
ProjectID: "test_project_id",
137+
AuthURL: "test_auth_url",
138+
DomainName: "test_domain",
139+
},
140+
RegionName: "test_region",
141+
}
142+
assert.NoError(t, err)
143+
assert.Equal(t, expectedCloud, cloud)
144+
assert.Equal(t, caCert, expectedcaCert)
86145

87146
err = client.Delete(context.TODO(), secret)
88147
assert.NoError(t, err)
@@ -99,14 +158,15 @@ func TestGetCloudFromSecretNotFound(t *testing.T) {
99158
secretNamespace := "nonexistent-namespace"
100159
expectedError := "secrets \"nonexistent-secret\" not found"
101160

102-
cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
161+
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
103162

104163
expectedErrorMessage := fmt.Sprintf("failed to get secret %s in namespace %s: %v", secretName, secretNamespace, expectedError)
105164

106165
assert.Error(t, err)
107166
assert.True(t, apierrors.IsNotFound(err))
108167
assert.Equal(t, clientconfig.Cloud{}, cloud)
109168
assert.EqualError(t, err, expectedErrorMessage)
169+
assert.Equal(t, []byte(nil), caCert)
110170
}
111171

112172
func TestGetCloudFromSecretMissingCloudsSecretKey(t *testing.T) {
@@ -131,11 +191,12 @@ func TestGetCloudFromSecretMissingCloudsSecretKey(t *testing.T) {
131191
err := client.Create(context.TODO(), secret)
132192
assert.NoError(t, err)
133193

134-
cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
194+
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
135195

136196
assert.Error(t, err)
137197
assert.EqualError(t, err, fmt.Sprintf("OpenStack credentials secret %s did not contain key %s", secretName, cloudsSecretKey))
138198
assert.Equal(t, clientconfig.Cloud{}, cloud)
199+
assert.Equal(t, []byte(nil), caCert)
139200

140201
err = client.Delete(context.TODO(), secret)
141202
assert.NoError(t, err)
@@ -178,11 +239,12 @@ clouds:
178239
err := client.Create(context.TODO(), secret)
179240
assert.NoError(t, err)
180241

181-
cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
242+
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
182243

183244
assert.Error(t, err)
184245
assert.EqualError(t, err, fmt.Sprintf("failed to find cloud %s in %s", cloudName, cloudsSecretKey))
185246
assert.Equal(t, clientconfig.Cloud{}, cloud)
247+
assert.Equal(t, []byte(nil), caCert)
186248

187249
err = client.Delete(context.TODO(), secret)
188250
assert.NoError(t, err)

0 commit comments

Comments
 (0)