Skip to content
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

Merged
merged 1 commit into from Jul 2, 2020
Merged

Enable token auth sync #326

merged 1 commit into from Jul 2, 2020

Conversation

fao89
Copy link
Member

@fao89 fao89 commented Jun 10, 2020

@pep8speaks
Copy link

pep8speaks commented Jun 10, 2020

Hello @fao89! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-07-01 20:54:12 UTC

@pulpbot
Copy link
Member

pulpbot commented Jun 10, 2020

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:
Copy link
Member Author

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

Copy link
Contributor

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:
Copy link
Member Author

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

@fao89
Copy link
Member Author

fao89 commented Jun 15, 2020

I had an issue here:
(building URL)
https://github.com/pulp/pulp_ansible/blob/master/pulp_ansible/app/tasks/collections.py#L340-L347

this endpoint https://ci.cloud.redhat.com/api/automation-hub/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/ returns:

{
    "href": "/pulp_ansible/galaxy/automation-hub/api/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/",
    "created_at": "2020-02-21T23:01:52.727205Z",
    "updated_at": "2020-02-21T23:01:52.727232Z",
    "namespace": "autohubtest2",
    "name": "collection_dep_a_hlrknkqw",
    "deprecated": false,
    "versions_url": "/pulp_ansible/galaxy/automation-hub/api/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/versions/",
    "highest_version": {
        "href": "/pulp_ansible/galaxy/automation-hub/api/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/versions/1.0.0/",
        "version": "1.0.0"
    }
}

so when getting versions_url it yields:
/pulp_ansible/galaxy/automation-hub/api/v3/collections/<namespace>/<name>/versions
https://ci.cloud.redhat.com/pulp_ansible/galaxy/automation-hub/api/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/versions/

but the URL should be:
/api/automation-hub/v3/collections/<namespace>/<name>/versions
https://ci.cloud.redhat.com/api/automation-hub/v3/collections/autohubtest2/collection_dep_a_hlrknkqw/versions/

@fao89 fao89 marked this pull request as ready for review June 15, 2020 18:49
@fao89 fao89 requested a review from bmbouter June 15, 2020 18:52
"""
Custom Downloader that automatically handles Token Based and Basic Authentication.

Additionally, use custom headers from DeclarativeArtifact.extra_data['headers']
Copy link
Member

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.

Copy link
Member Author

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

@bmbouter
Copy link
Member

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?

@fao89
Copy link
Member Author

fao89 commented Jun 15, 2020

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

Copy link
Contributor

@alikins alikins left a 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)
Copy link
Contributor

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:

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.

Copy link
Contributor

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.

Copy link
Member

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:
Copy link
Contributor

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.

return await super()._run(extra_data=extra_data)

token = await self.update_token()
headers = {"Authorization": "Bearer {token}".format(token=token)}
Copy link
Contributor

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)

@fao89 fao89 requested a review from a team June 23, 2020 14:38
@fao89
Copy link
Member Author

fao89 commented Jun 24, 2020

I wasn't able to make the CI work, because it requires VPN to access https://ci.cloud.redhat.com/:
image
it was not about the token on travis CI

Comment on lines 44 to 55
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)}
Copy link
Member Author

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

Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fao89 fao89 requested a review from bmbouter June 29, 2020 19:43
Comment on lines +87 to +92
@skip_if(bool, "AH_token", False)
def test_install_collection_with_token_from_automation_hub(self):
Copy link
Member Author

@fao89 fao89 Jun 30, 2020

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

Comment on lines +135 to +103
@skip_if(bool, "GH_token", False)
def test_install_collection_with_token_from_galaxy(self):
Copy link
Member Author

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

@@ -0,0 +1 @@
Enable token auth sync
Copy link
Member

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:

@@ -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``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

class TokenAuthHttpDownloader(HttpDownloader):
"""
Custom Downloader that automatically handles Token Based and Basic Authentication.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra line not needed


"""

token_lock = asyncio.Lock()
Copy link
Member

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/

"""
Initialize the downloader.
"""
self.remote = kwargs.pop("remote")
Copy link
Member

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

headers = {"Authorization": "Bearer {token}".format(token=token)}

# aiohttps does not allow to send auth argument and auth header together
self.session._default_auth = None
Copy link
Member

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.

Comment on lines +59 to +64
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
Copy link
Member

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.

Copy link
Member Author

@fao89 fao89 Jul 1, 2020

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

)
return self._download_factory

def get_downloader(self, remote_artifact=None, url=None, **kwargs):
Copy link
Member

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.

try:
return self._download_factory
except AttributeError:
self._download_factory = DownloaderFactory(
Copy link
Member

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

@@ -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’ "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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.

max_length=255,
)
token = serializers.CharField(
help_text=_("The token key to use for authentication."),
Copy link
Member

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.
Copy link
Member

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()
Copy link
Member

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.

@fao89 fao89 requested a review from bmbouter July 2, 2020 17:17
Copy link
Member

@bmbouter bmbouter left a 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!

@bmbouter bmbouter merged commit 41e4ef0 into pulp:master Jul 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants