diff --git a/main.go b/main.go index 1015072f55..2a8f59d9b0 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,7 @@ var ( lbProvider string caCertsPath string logOptions = logs.NewOptions() + useSystemRootCAs bool ) func init() { @@ -135,6 +136,8 @@ func InitFlags(fs *pflag.FlagSet) { "The name of the load balancer provider (amphora or ovn) to use (defaults to amphora).") fs.StringVar(&caCertsPath, "ca-certs", "", "The path to a PEM-encoded CA Certificate file to supply as default for each request.") + + fs.BoolVar(&useSystemRootCAs, "use-system-root-ca", false, "Use system root CAs rather than specifying them in resources.") } func main() { @@ -226,11 +229,13 @@ func setupChecks(mgr ctrl.Manager) { } func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) { + scopeFactory := scope.NewProviderScopeFactory(useSystemRootCAs) + if err := (&controllers.OpenStackClusterReconciler{ Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor("openstackcluster-controller"), WatchFilterValue: watchFilterValue, - ScopeFactory: scope.ScopeFactory, + ScopeFactory: scopeFactory, CaCertificates: caCerts, }).SetupWithManager(ctx, mgr, concurrency(openStackClusterConcurrency)); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OpenStackCluster") @@ -240,7 +245,7 @@ func setupReconcilers(ctx context.Context, mgr ctrl.Manager, caCerts []byte) { Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor("openstackmachine-controller"), WatchFilterValue: watchFilterValue, - ScopeFactory: scope.ScopeFactory, + ScopeFactory: scopeFactory, CaCertificates: caCerts, }).SetupWithManager(ctx, mgr, concurrency(openStackMachineConcurrency)); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OpenStackMachine") diff --git a/pkg/scope/provider.go b/pkg/scope/provider.go index e3204a9d8c..88ef0c6d2b 100644 --- a/pkg/scope/provider.go +++ b/pkg/scope/provider.go @@ -44,9 +44,19 @@ const ( caSecretKey = "cacert" ) -type providerScopeFactory struct{} +type providerScopeFactory struct { + // useSystemRootCAs allows use of the public CA pool provided by the distroless + // base image. Handy for public clouds. + useSystemRootCAs bool +} -func (providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine, defaultCACert []byte, logger logr.Logger) (Scope, error) { +func NewProviderScopeFactory(useSystemRootCAs bool) Factory { + return &providerScopeFactory{ + useSystemRootCAs: useSystemRootCAs, + } +} + +func (f providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine, defaultCACert []byte, logger logr.Logger) (Scope, error) { var cloud clientconfig.Cloud var caCert []byte @@ -62,10 +72,10 @@ func (providerScopeFactory) NewClientScopeFromMachine(ctx context.Context, ctrlC caCert = defaultCACert } - return NewProviderScope(cloud, caCert, logger) + return NewProviderScope(cloud, caCert, f.useSystemRootCAs, logger) } -func (providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlClient client.Client, openStackCluster *infrav1.OpenStackCluster, defaultCACert []byte, logger logr.Logger) (Scope, error) { +func (f providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlClient client.Client, openStackCluster *infrav1.OpenStackCluster, defaultCACert []byte, logger logr.Logger) (Scope, error) { var cloud clientconfig.Cloud var caCert []byte @@ -81,7 +91,7 @@ func (providerScopeFactory) NewClientScopeFromCluster(ctx context.Context, ctrlC caCert = defaultCACert } - return NewProviderScope(cloud, caCert, logger) + return NewProviderScope(cloud, caCert, f.useSystemRootCAs, logger) } type providerScope struct { @@ -91,8 +101,8 @@ type providerScope struct { logger logr.Logger } -func NewProviderScope(cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (Scope, error) { - providerClient, clientOpts, projectID, err := NewProviderClient(cloud, caCert, logger) +func NewProviderScope(cloud clientconfig.Cloud, caCert []byte, useSystemRootCAs bool, logger logr.Logger) (Scope, error) { + providerClient, clientOpts, projectID, err := NewProviderClient(cloud, caCert, useSystemRootCAs, logger) if err != nil { return nil, err } @@ -133,7 +143,7 @@ func (s *providerScope) NewLbClient() (clients.LbClient, error) { return clients.NewLbClient(s.providerClient, s.providerClientOpts) } -func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, logger logr.Logger) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) { +func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, useSystemRootCAs bool, logger logr.Logger) (*gophercloud.ProviderClient, *clientconfig.ClientOpts, string, error) { clientOpts := new(clientconfig.ClientOpts) if cloud.AuthInfo != nil { clientOpts.AuthInfo = cloud.AuthInfo @@ -153,14 +163,25 @@ func NewProviderClient(cloud clientconfig.Cloud, caCert []byte, logger logr.Logg } config := &tls.Config{ - RootCAs: x509.NewCertPool(), MinVersion: tls.VersionTLS12, } if cloud.Verify != nil { config.InsecureSkipVerify = !*cloud.Verify } - if caCert != nil { - config.RootCAs.AppendCertsFromPEM(caCert) + + if useSystemRootCAs { + roots, err := x509.SystemCertPool() + if err != nil { + return nil, nil, "", fmt.Errorf("failed to load system root CAs: %v", err) + } + + config.RootCAs = roots + } else { + config.RootCAs = x509.NewCertPool() + + if caCert != nil { + config.RootCAs.AppendCertsFromPEM(caCert) + } } provider.HTTPClient.Transport = &http.Transport{Proxy: http.ProxyFromEnvironment, TLSClientConfig: config} diff --git a/pkg/scope/scope.go b/pkg/scope/scope.go index b3f6ca728c..4d1d97c782 100644 --- a/pkg/scope/scope.go +++ b/pkg/scope/scope.go @@ -26,9 +26,6 @@ import ( "sigs.k8s.io/cluster-api-provider-openstack/pkg/clients" ) -// ScopeFactory is the default scope factory. It generates service clients which make OpenStack API calls against a running cloud. -var ScopeFactory Factory = providerScopeFactory{} - // Factory instantiates a new Scope using credentials from either a cluster or a machine. type Factory interface { NewClientScopeFromMachine(ctx context.Context, ctrlClient client.Client, openStackMachine *infrav1.OpenStackMachine, defaultCACert []byte, logger logr.Logger) (Scope, error) diff --git a/test/e2e/shared/openstack.go b/test/e2e/shared/openstack.go index 2c69426144..26fd643eac 100644 --- a/test/e2e/shared/openstack.go +++ b/test/e2e/shared/openstack.go @@ -536,7 +536,7 @@ func getProviderClient(e2eCtx *E2EContext, openstackCloud string) (*gophercloud. clouds := getParsedOpenStackCloudYAML(openStackCloudYAMLFile) cloud := clouds.Clouds[openstackCloud] - providerClient, clientOpts, projectID, err := scope.NewProviderClient(cloud, nil, logr.Discard()) + providerClient, clientOpts, projectID, err := scope.NewProviderClient(cloud, nil, false, logr.Discard()) if err != nil { return nil, nil, nil, err }