-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixing cookie based authentication #1091
base: main
Are you sure you want to change the base?
Conversation
1 similar comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks sensible.
Unit tests would be great, unless the existing unit test actually cover this change.
I wonder how to validate this change against real Juju.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the contribution! It looks good and it'll be great to get this working again.
As @dimaqq mentioned, it would be good to get some tests working for this. There are some disabled tests in /tests/integration/test_macaroon_auth.py
that should work again with this fix. Would you be able to get the relevant ones from there passing and remove the skip decorators on them?
juju/client/gocookies.py
Outdated
import pyrfc3339 | ||
|
||
|
||
# There are certain subtle differences between the Go and Python |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than monkey patching here, would it be possible to get the inherit from the DefaultCookiePolicy, override the relevent methods then pass it as a policy kwarg to cookiejar.FileCookieJar
?
I believe this should achive the same effect without monkey patching the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was some complication which I have forgotten. I can look into it again later today.
@@ -962,7 +967,7 @@ async def login(self): | |||
params['credentials'] = self.password | |||
else: | |||
macaroons = _macaroons_for_domain(self.bakery_client.cookies, | |||
self.endpoint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I fully get the intent behind this from inspection. What is the difference here between the endpoint and the cookie domain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because when you are using macaroons from the bakery client, you are actually using cookies that were originally put in the cookie jar by the juju client. The juju client does not use the endpoint as the cookie domain. Instead it uses either the controller's UUID (if the controller is self signed) or its SNIHostName. If we don't pick the correct cookie domain then pylibjuju will not find the cookie and it will fail to authenticate with the controller. I believe this is because if you have a self signed HA controller (for example) then there will be three different potential endpoints, so if you use that for your cookie domain, then your coookie will only work with one of the endpoints. As an example: you might have an endpoint which is 192.168.3.14, but the cookie in the jar has the domain "791fa163-16bd-4726-9c58-7c360cc133b2".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah fair, good work on the digging! Seems like a bug that this wasn't implemented the same way then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, the juju code is
cookieURL := api.CookieURLFromHost(api.PerferredHost(info))
info.Macaroons = httpbakery.MacaroonsForURL(cookieJar, cookieURL)
where PreferredHost is either the SNIHostName or controller UUID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wallyworld can you look over this PR, because you did a lot of work around macroons in the go side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks ok. Left a couple of comments to provide a little bit of extra context from the Juju side. I'll leave it to others to agree/disagree with the trade-offs around the cookie jar behaviour, suffice to say it's progress and if it works from the perspective of "don't let perfect be the enemy of good", then it seems ok as a step forward, especially since upstream Juju changes are out of scope here.
@@ -871,7 +876,7 @@ async def _connect_with_login(self, endpoints): | |||
# a few times. | |||
for i in range(0, 2): | |||
result = (await self.login())['response'] | |||
macaroonJSON = result.get('discharge-required') | |||
macaroonJSON = result.get('bakery-discharge-required') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the casual observer - newer Juju controllers (2.9 onwards) use the "bakery-discharge-required" attribute.
@@ -962,7 +967,7 @@ async def login(self): | |||
params['credentials'] = self.password | |||
else: | |||
macaroons = _macaroons_for_domain(self.bakery_client.cookies, | |||
self.endpoint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, the juju code is
cookieURL := api.CookieURLFromHost(api.PerferredHost(info))
info.Macaroons = httpbakery.MacaroonsForURL(cookieJar, cookieURL)
where PreferredHost is either the SNIHostName or controller UUID.
This PR needs to be rebased since #1186 has been merged. |
Description
At present, cookie based authentication does not work in pylibjuju. This is due to a number of bugs. This PR intends to fix many of these issues so that pylibjuju can use the juju client's cookies for authentication.
Fixes:
http.cookiejar.DefaultPolicy
was fixed with monkey patching.I have also cherry picked a commit from @Aflynn50 from a different ongoing PR.
Fixes: #1061 #1081
QA Steps
I have had some difficulty with running the tests locally. I am not sure what sort of setup they want to run in.
What I have tested manually is the following:
Things I have been unable to test that are affected by this PR:
Notes & Discussion
The monkey patches in
gocookies.py
are not a good look and they are somewhat brittle, but I believe it will take a lot more work to fix theCookiePolicy
if we wish to retain all the other sane behaviors fromhttp.cookiejar.DefaultCookiePolicy
. An alternative is to use an emptyCookiePolicy
and subclass it to only check for domain equality and path. The problem the monkey patch fixes is caused by incompatible cookiejars. The juju client uses https://github.com/juju/persistent-cookiejar which permits domain fields without dots under the condition that the domain matches exactly with the request's host. Python's implementation instead rewrites the request's host by appending ".local", which makes the comparison fail.There are still issues with CA certificates when requiring discharges from the juju controller. This is due to intricacies of the HTTP handling in pymacaroonbakery. It may require substantial changes in pymacaroonbakery, but due to a number of bugfixes in this PR it is not essential for most uses (self signed external identity providers are the exception). The lack of a fix does mean that if a user is not logged in on a local user, and tries to connect to the controller, then they will be met with the wrong exception. The intended outcome is to require an interaction to enter a password, but this is not implemented in pymacaroonbakery. In any case, allowing self signed certificates requires either a hack where pylibjuju sets an environment variable and writes a cacert to a temporary file, or making changes in pymacaroonbakery.