Skip to content

Commit

Permalink
Allow Use of Public CAs
Browse files Browse the repository at this point in the history
As a public cloud operator it strikes me as odd that I have to supply a
CA certificate for every cluster that's created.  This leads to a bunch
of code that has to Dial the endpoint and extract the root CA (rather
than hard coding it - ugh!).  The whole point of the distroless base
container image is to distribute TLS root CAs, so allow the option to
use what's already present.
  • Loading branch information
spjmurray committed Jul 14, 2023
1 parent 9d183bd commit 2d5ec5c
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 17 deletions.
9 changes: 7 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ var (
lbProvider string
caCertsPath string
logOptions = logs.NewOptions()
useSystemRootCAs bool
)

func init() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down
43 changes: 32 additions & 11 deletions pkg/scope/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down
3 changes: 0 additions & 3 deletions pkg/scope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/shared/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 2d5ec5c

Please sign in to comment.