Skip to content

Commit

Permalink
Support specifying the CA cert or adding verify=false into clouds.yaml
Browse files Browse the repository at this point in the history
Signed-off-by: michal.gubricky <michal.gubricky@dnation.cloud>
  • Loading branch information
michal-gubricky committed Apr 12, 2024
1 parent e73ef8e commit cd90634
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 13 deletions.
49 changes: 40 additions & 9 deletions internal/controller/openstacknodeimagerelease_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ package controller

import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"time"

"github.com/gophercloud/gophercloud"
Expand Down Expand Up @@ -53,6 +56,7 @@ const (
defaultCloudName = "openstack"
cloudNameSecretKey = "cloudName"
cloudsSecretKey = "clouds.yaml"
caSecretKey = "cacert"
waitForImageBecomeActive = 30 * time.Second
)

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

// Get OpenStack cloud config from sercet
cloud, err := r.getCloudFromSecret(ctx, openstacknodeimagerelease.Namespace, openstacknodeimagerelease.Spec.IdentityRef.Name)
cloud, caCert, err := r.getCloudFromSecret(ctx, openstacknodeimagerelease.Namespace, openstacknodeimagerelease.Spec.IdentityRef.Name)
if err != nil {
if apierrors.IsNotFound(err) {
conditions.MarkFalse(openstacknodeimagerelease,
Expand All @@ -122,8 +126,28 @@ func (r *OpenStackNodeImageReleaseReconciler) Reconcile(ctx context.Context, req

conditions.MarkTrue(openstacknodeimagerelease, apiv1alpha1.CloudAvailableCondition)

httpClient := &http.Client{}

tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
}

if cloud.Verify != nil && !*cloud.Verify {
tlsConfig.InsecureSkipVerify = true
} else if caCert != nil {
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if ok {
tlsConfig.RootCAs = caCertPool
}
}

httpClient.Transport = &http.Transport{
TLSClientConfig: tlsConfig,
}

// Create an OpenStack provider client
opts := &clientconfig.ClientOpts{AuthInfo: cloud.AuthInfo}
opts := &clientconfig.ClientOpts{AuthInfo: cloud.AuthInfo, HTTPClient: httpClient}
providerClient, err := clientconfig.AuthenticatedClient(opts)
if err != nil {
record.Warnf(openstacknodeimagerelease, "OpenStackProviderClientNotSet", err.Error())
Expand Down Expand Up @@ -293,7 +317,7 @@ func (r *OpenStackNodeImageReleaseReconciler) Reconcile(ctx context.Context, req
return ctrl.Result{}, nil
}

func (r *OpenStackNodeImageReleaseReconciler) getCloudFromSecret(ctx context.Context, secretNamespace, secretName string) (clientconfig.Cloud, error) {
func (r *OpenStackNodeImageReleaseReconciler) getCloudFromSecret(ctx context.Context, secretNamespace, secretName string) (clientconfig.Cloud, []byte, error) {
var clouds clientconfig.Clouds
emptyCloud := clientconfig.Cloud{}
var cloudName string
Expand All @@ -304,31 +328,38 @@ func (r *OpenStackNodeImageReleaseReconciler) getCloudFromSecret(ctx context.Con
Name: secretName,
}, secret)
if err != nil {
return emptyCloud, fmt.Errorf("failed to get secret %s in namespace %s: %w", secretName, secretNamespace, err)
return emptyCloud, nil, fmt.Errorf("failed to get secret %s in namespace %s: %w", secretName, secretNamespace, err)
}

content, ok := secret.Data[cloudNameSecretKey]
if !ok {
cloudName = defaultCloudName
} else {
if err := yaml.Unmarshal(content, &cloudName); err != nil {
return emptyCloud, fmt.Errorf("failed to unmarshal cloudName stored in secret %s: %w", secretName, err)
return emptyCloud, nil, fmt.Errorf("failed to unmarshal cloudName stored in secret %s: %w", secretName, err)
}
}

content, ok = secret.Data[cloudsSecretKey]
if !ok {
return emptyCloud, fmt.Errorf("OpenStack credentials secret %s did not contain key %s", secretName, cloudsSecretKey)
return emptyCloud, nil, fmt.Errorf("OpenStack credentials secret %s did not contain key %s", secretName, cloudsSecretKey)
}
if err = yaml.Unmarshal(content, &clouds); err != nil {
return emptyCloud, fmt.Errorf("failed to unmarshal clouds credentials stored in secret %s: %w", secretName, err)
return emptyCloud, nil, fmt.Errorf("failed to unmarshal clouds credentials stored in secret %s: %w", secretName, err)
}

cloud, ok := clouds.Clouds[cloudName]
if !ok {
return emptyCloud, fmt.Errorf("failed to find cloud %s in %s", cloudName, cloudsSecretKey)
return emptyCloud, nil, fmt.Errorf("failed to find cloud %s in %s", cloudName, cloudsSecretKey)
}

// get caCert
caCert, ok := secret.Data[caSecretKey]
if !ok {
return cloud, nil, nil
}
return cloud, nil

return cloud, caCert, nil
}

func getImageID(imagesClient *gophercloud.ServiceClient, imageCreateOps *apiv1alpha1.CreateOpts) (string, error) {
Expand Down
70 changes: 66 additions & 4 deletions internal/controller/openstacknodeimagerelease_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ clouds:
Client: client,
}

cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)

expectedCloud := clientconfig.Cloud{
AuthInfo: &clientconfig.AuthInfo{
Expand All @@ -83,6 +83,65 @@ clouds:
}
assert.NoError(t, err)
assert.Equal(t, expectedCloud, cloud)
assert.Equal(t, []byte(nil), caCert)

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

func TestGetCloudFromSecretWithCaCert(t *testing.T) {
client := fake.NewClientBuilder().Build()

secretName := "test-secret"
secretNamespace := "test-namespace"
cloudsYAML := `
clouds:
openstack:
auth:
username: test_user
password: test_password
project_name: test_project
project_id: test_project_id
auth_url: test_auth_url
domain_name: test_domain
region_name: test_region
`
expectedcaCert := []byte("test-ca-cert")

secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: secretNamespace,
},
Data: map[string][]byte{
cloudsSecretKey: []byte(cloudsYAML),
caSecretKey: expectedcaCert,
},
Type: corev1.SecretTypeOpaque,
}
err := client.Create(context.TODO(), secret)
assert.NoError(t, err)

r := &OpenStackNodeImageReleaseReconciler{
Client: client,
}

cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)

expectedCloud := clientconfig.Cloud{
AuthInfo: &clientconfig.AuthInfo{
Username: "test_user",
Password: "test_password",
ProjectName: "test_project",
ProjectID: "test_project_id",
AuthURL: "test_auth_url",
DomainName: "test_domain",
},
RegionName: "test_region",
}
assert.NoError(t, err)
assert.Equal(t, expectedCloud, cloud)
assert.Equal(t, caCert, expectedcaCert)

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

cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)

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

assert.Error(t, err)
assert.True(t, apierrors.IsNotFound(err))
assert.Equal(t, clientconfig.Cloud{}, cloud)
assert.EqualError(t, err, expectedErrorMessage)
assert.Equal(t, []byte(nil), caCert)
}

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

cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)

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

err = client.Delete(context.TODO(), secret)
assert.NoError(t, err)
Expand Down Expand Up @@ -178,11 +239,12 @@ clouds:
err := client.Create(context.TODO(), secret)
assert.NoError(t, err)

cloud, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)
cloud, caCert, err := r.getCloudFromSecret(context.TODO(), secretNamespace, secretName)

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

err = client.Delete(context.TODO(), secret)
assert.NoError(t, err)
Expand Down

0 comments on commit cd90634

Please sign in to comment.