We have a regulatory requirement to enable multi-factor auth in our OpenStack deployment, so we want to require users to supply client certificates in addition to their regular password-based credentials. In the upstream clients based on python-keystoneclient, this usecase is supported via the OS_CERT and OS_KEY environment variables (or respective clouds.yaml entries).
The gophercloud/utils API somewhat supports OS_CERT/OS_KEY and their sibling OS_CACERT , but only in clientconfig.NewServiceClient, not in the much more important clientconfig.AuthOptions and clientconfig.AuthenticatedClient calls.
For us, that means that the only choice in the short term is to patch client certs into the ProviderClient.HTTPClient manually, like in sapcc/limesctl#122. This feels like something that Gophercloud should cover.
The main issue is that both clientconfig.AuthOptions and clientconfig.AuthenticatedClient are constrained by the capabilities of the gophercloud.AuthOptions type, which does not have any API surface for TLS certificates. In clientconfig.AuthOptions, the constraint is visible from the outside since gophercloud.AuthOptions is the return value for this function. In clientconfig.AuthenticatedClient, the constraint is less obvious: Its implementation defers most of the work to openstack.AuthenticatedClient which takes a single gophercloud.AuthOptions argument.
Proposal
Right now, as far as I can see, the best that we can do is to change Gophercloud to:
- add new fields to
gophercloud.AuthOptions that cover TLS client certs and CA certs
- option 1: in the same way that the env vars as structured (
ClientCertFile, ClientKeyFile, CACertFile string) -> advantage: clear semantics
- option 2: allow a custom
http.Client to be injected (HTTPClient *http.Client) -> advantage: same pattern as in clientconfig.ClientOpts, so we would have the chance to also fix this field to work properly all the time
- have
openstack.AuthenticatedClient respect those fields (i.e. set up the ProviderClient.HTTPClient thusly before moving on into openstack.Authenticate)
Once Gophercloud has cut a new release after these changes, gophercloud/utils can be changed to:
- have
clientconfig.AuthOptions fill the new fields in its return value appropriately from env vars and clouds.yaml
- have
clientconfig.AuthOptions carry forward the ClientOpts.HTTPClient field into its return value (if option 2 was chosen above)
The ugly part here is that there are several intended paths to obtain a gophercloud.ProviderClient, but we can only fix some of them (specifically, all that go through openstack.AuthenticatedClient). To support the new AuthOptions fields in all possible paths, openstack.NewClient would have to be amended, but its API is too narrow.
When Gophercloud breaks API compatibility next time, I propose to:
- change
openstack.NewClient() from taking a single endpoint string argument to taking a struct of at least endpoint string plus the TLS-related options
- change
gophercloud.AuthOptions to have that struct type as a field in it (thus grouping all the options together that are needed for the NewClient step)
I'm willing to contribute dev time on our team to implement these changes, but I would like to align with you first before starting on the implementation.
cc @talal @databus23
We have a regulatory requirement to enable multi-factor auth in our OpenStack deployment, so we want to require users to supply client certificates in addition to their regular password-based credentials. In the upstream clients based on
python-keystoneclient, this usecase is supported via the OS_CERT and OS_KEY environment variables (or respectiveclouds.yamlentries).The gophercloud/utils API somewhat supports OS_CERT/OS_KEY and their sibling OS_CACERT , but only in
clientconfig.NewServiceClient, not in the much more importantclientconfig.AuthOptionsandclientconfig.AuthenticatedClientcalls.For us, that means that the only choice in the short term is to patch client certs into the
ProviderClient.HTTPClientmanually, like in sapcc/limesctl#122. This feels like something that Gophercloud should cover.The main issue is that both
clientconfig.AuthOptionsandclientconfig.AuthenticatedClientare constrained by the capabilities of thegophercloud.AuthOptionstype, which does not have any API surface for TLS certificates. Inclientconfig.AuthOptions, the constraint is visible from the outside sincegophercloud.AuthOptionsis the return value for this function. Inclientconfig.AuthenticatedClient, the constraint is less obvious: Its implementation defers most of the work toopenstack.AuthenticatedClientwhich takes a singlegophercloud.AuthOptionsargument.Proposal
Right now, as far as I can see, the best that we can do is to change Gophercloud to:
gophercloud.AuthOptionsthat cover TLS client certs and CA certsClientCertFile, ClientKeyFile, CACertFile string) -> advantage: clear semanticshttp.Clientto be injected (HTTPClient *http.Client) -> advantage: same pattern as inclientconfig.ClientOpts, so we would have the chance to also fix this field to work properly all the timeopenstack.AuthenticatedClientrespect those fields (i.e. set up theProviderClient.HTTPClientthusly before moving on intoopenstack.Authenticate)Once Gophercloud has cut a new release after these changes, gophercloud/utils can be changed to:
clientconfig.AuthOptionsfill the new fields in its return value appropriately from env vars and clouds.yamlclientconfig.AuthOptionscarry forward theClientOpts.HTTPClientfield into its return value (if option 2 was chosen above)The ugly part here is that there are several intended paths to obtain a
gophercloud.ProviderClient, but we can only fix some of them (specifically, all that go throughopenstack.AuthenticatedClient). To support the new AuthOptions fields in all possible paths,openstack.NewClientwould have to be amended, but its API is too narrow.When Gophercloud breaks API compatibility next time, I propose to:
openstack.NewClient()from taking a singleendpoint stringargument to taking a struct of at leastendpoint stringplus the TLS-related optionsgophercloud.AuthOptionsto have that struct type as a field in it (thus grouping all the options together that are needed for theNewClientstep)I'm willing to contribute dev time on our team to implement these changes, but I would like to align with you first before starting on the implementation.
cc @talal @databus23