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
Enable token auth sync #326
Conversation
|
Attached issue: https://pulp.plan.io/issues/6540 |
| # aiohttps does not allow to send auth argument and auth header together | ||
| self.session._default_auth = None | ||
|
|
||
| async with self.session.get(self.url, headers=headers, proxy=self.proxy) as response: |
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 stage env, I had to add ssl=False
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.
Possible to add the full ca_chain for ci / stage will work as well.
| "refresh_token": self.remote.token, | ||
| } | ||
| url = self.remote.auth_url | ||
| async with self.session.post(url, data=form_payload, raise_for_status=True) as response: |
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 stage env, I had to add ssl=False
|
I had an issue here: this endpoint https://ci.cloud.redhat.com/api/automation-hub/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/ returns: so when getting but the URL should be: |
pulp_ansible/app/downloaders.py
Outdated
| """ | ||
| Custom Downloader that automatically handles Token Based and Basic Authentication. | ||
|
|
||
| Additionally, use custom headers from DeclarativeArtifact.extra_data['headers'] |
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.
Do these come from DeclarativeArtifact? I don't see them set anywhere.
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 copied from pulp_container and forgot to remove it
|
Can some user facing docs be added? We already have a Collection section on syncing from galaxy.ansible.com, so I think this would be a "Syncing from cloud.redhat.com" as as an example. @fao89 wdyt about adding this? |
I agree, I'll add it |
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 think this may have issues against galaxy.ansible.com
| @@ -227,6 +229,60 @@ class CollectionRemote(Remote): | |||
| TYPE = "collection" | |||
|
|
|||
| requirements_file = models.TextField(null=True, max_length=255) | |||
| auth_url = models.CharField(null=True, max_length=255) | |||
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 was thinking this may need to be split info two Remote subclasses. One for the 'v2' and galaxy.ansible.com API, and another for Automation-hub / cloud.redhat.com / galaxy_ng.
Partly because "community" galaxy.ansible.com and cloud.redhat.com need different auth tokens.
cloud.redhat.com needs:
- the 'auth_url' (pointing at https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token SSO server)
- The value of "token" is a "refresh_token" in from SSO (the token a user gets from 'GET API TOKEN' on the cloud.redhat.com automation-hub ui).
- The token received from SSO is used in 'Autorization: Bearer '
community / galaxy.ansible.com / maybe standalone galaxy_ng
- No 'auth_url' is required. For ansible-galaxy client the presence of a auth_url is what determines if SSO style auth is used and what format the authorization token takes.
- The value of "token" is a drf auth token
- The token is used in an 'Authorization: Token ' header.
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.
name wise, I was thinking more or less "CollectionRemote" for v2 / community galaxy.ansible.com remotes since it exists already.
And 'AutomationHubRemote' for Remotes for talking to automation-hub / cloud.redhat.com.
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.
The differences in the technologies of the tokens (to me) motivates splitting up the Remote into two.
| # aiohttps does not allow to send auth argument and auth header together | ||
| self.session._default_auth = None | ||
|
|
||
| async with self.session.get(self.url, headers=headers, proxy=self.proxy) as response: |
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.
Possible to add the full ca_chain for ci / stage will work as well.
pulp_ansible/app/downloaders.py
Outdated
| return await super()._run(extra_data=extra_data) | ||
|
|
||
| token = await self.update_token() | ||
| headers = {"Authorization": "Bearer {token}".format(token=token)} |
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 think this will fail if the CollectionRemote is pointing at galaxy.ansible.com (and I believe, if pointed against a standalone galaxy_ng that is using drf style 'Authorization: Token ' style auth headers)
|
I wasn't able to make the CI work, because it requires VPN to access https://ci.cloud.redhat.com/: |
pulp_ansible/app/downloaders.py
Outdated
| if not self.remote.auth_url: | ||
| headers = {"Authorization": self.remote.token} | ||
| else: | ||
| token = await self.update_token_from_auth_url() | ||
| headers = {"Authorization": "Bearer {token}".format(token=token)} |
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.
Without auth_url will be considered as community / galaxy.ansible.com / maybe standalone galaxy_ng
With auth_url will be considered as cloud.redhat.com
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 guess this is simpler than splitting the remotes. The only reservation I have is if users will be confused about this. I guess we could make this clear in the serializer help text.
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 just seeing this comment now, maybe we should leave it as one remote.
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.
pulp_container does something similar:
https://github.com/pulp/pulp_container/blob/master/pulp_container/app/downloaders.py#L25
| @skip_if(bool, "AH_token", False) | ||
| def test_install_collection_with_token_from_automation_hub(self): |
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.
Tested locally and it is working! It will only run on pulp org, as travis does not set variables on PRs
@bmbouter @ironfroggy @chouseknecht
| @skip_if(bool, "GH_token", False) | ||
| def test_install_collection_with_token_from_galaxy(self): |
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.
It is more like a placeholder, as I don't know which collection requires token auth to be synced, I put pulp.pulp_installer here.
@bmbouter @ironfroggy @chouseknecht
CHANGES/6540.feature
Outdated
| @@ -0,0 +1 @@ | |||
| Enable token auth sync | |||
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.
Can this get more detail. As a user I would not understand this. Here are a few concrete suggestions:
- Call out the new fields
- Indicate which remote servers these fields can work with, e.g.
galaxy.ansible.comandcloud.redhat.com. - Relate the new fields to the ansible-galaxy CLI documentation here https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#configuring-the-ansible-galaxy-client
docs/workflows/collections.rst
Outdated
| @@ -136,6 +136,30 @@ Remote GET Response:: | |||
| "url": "https://galaxy-dev.ansible.com/api/v2/collections/testing/ansible_testing_content/", | |||
| } | |||
|
|
|||
| For content sources token authentication. You can provide the ``token`` and/or ``auth_url``. | |||
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 content sources token authentication. You can provide the ``token`` and/or ``auth_url``. | |
| For remote sources that require authentication, tokens can be used. You can provide the ``token`` and/or ``auth_url``. |
^ still needs line wrapping
Also finding a way to https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#configuring-the-ansible-galaxy-client here would be good too.
pulp_ansible/app/downloaders.py
Outdated
| class TokenAuthHttpDownloader(HttpDownloader): | ||
| """ | ||
| Custom Downloader that automatically handles Token Based and Basic Authentication. | ||
|
|
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.
extra line not needed
pulp_ansible/app/downloaders.py
Outdated
|
|
||
| """ | ||
|
|
||
| token_lock = asyncio.Lock() |
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.
class attributes tend to be in all caps so /token_lock/TOKEN_LOCK/
pulp_ansible/app/downloaders.py
Outdated
| """ | ||
| Initialize the downloader. | ||
| """ | ||
| self.remote = kwargs.pop("remote") |
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 definitly how the current ones implement this, but at a pulpcore we were hoping to have it be done differently in the future. This pattern would be replaced with a subclassed DownloadFactory and the kwargs would become positional args passed here instead. I wrote more about this change here: https://pulp.plan.io/issues/6965#note-2
pulp_ansible/app/downloaders.py
Outdated
| headers = {"Authorization": "Bearer {token}".format(token=token)} | ||
|
|
||
| # aiohttps does not allow to send auth argument and auth header together | ||
| self.session._default_auth = None |
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 could be better handled in the subclassed DownloadFactory also.
| async with self.session.get(self.url, headers=headers, proxy=self.proxy) as response: | ||
| response.raise_for_status() | ||
| to_return = await self._handle_response(response) | ||
| await response.release() | ||
|
|
||
| if self._close_session_on_finalize: | ||
| self.session.close() | ||
| return to_return |
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.
You could call super() to receive all of this instead.
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 couldn't, pulpcore is different:
async with self.session.get(self.url, proxy=self.proxy, auth=self.auth) as response:it uses auth and I use headers
https://github.com/pulp/pulpcore/blob/master/pulpcore/download/http.py#L197
pulp_ansible/app/models.py
Outdated
| ) | ||
| return self._download_factory | ||
|
|
||
| def get_downloader(self, remote_artifact=None, url=None, **kwargs): |
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.
Using the suggestion to subclass DownloadFactory would eliminate this method also.
pulp_ansible/app/models.py
Outdated
| try: | ||
| return self._download_factory | ||
| except AttributeError: | ||
| self._download_factory = DownloaderFactory( |
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 where you would use your subclassed DownloaderFactory instead
pulp_ansible/app/serializers.py
Outdated
| @@ -95,6 +95,21 @@ class CollectionRemoteSerializer(RemoteSerializer): | |||
| required=False, | |||
| allow_null=True, | |||
| ) | |||
| auth_url = serializers.CharField( | |||
| help_text=_( | |||
| "The URL of a Keycloak server ‘token_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.
| "The URL of a Keycloak server ‘token_endpoint’ " | |
| "The URL to receive a session token from, e.g. used with Automation Hub." |
Can we also add a sentence like: see https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#configuring-the-ansible-galaxy-client for more details.
pulp_ansible/app/serializers.py
Outdated
| max_length=255, | ||
| ) | ||
| token = serializers.CharField( | ||
| help_text=_("The token key to use for authentication."), |
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.
Can we also add a sentence like: See https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#configuring-the-ansible-galaxy-client for more details.
| monitor_task(sync_response.task) | ||
| repo = self.repo_api.read(repo.pulp_href) | ||
|
|
||
| # Create a distribution. |
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.
You could probably just check the unit counts at this point instead, but I'm ok w/ the rest of this test also even though it's duplicated in various places.
| repo = self.repo_api.read(repo.pulp_href) | ||
|
|
||
| # Create a distribution. | ||
| body = gen_distribution() |
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.
You could probably just check the unit counts at this point instead, but I'm ok w/ the rest of this test also even though it's duplicated in various places.
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 really excellent. You've implemented the suggested pattern perfectly. Thank you!

https://pulp.plan.io/issues/6540
closes #6540