<div dir="ltr">These are great questions. I'll try to keep my responses short to promote more discussion.<br><br><div class="gmail_quote"><div dir="ltr">On Mon, Nov 5, 2018 at 3:21 PM Simon Baatz <<a href="mailto:gmbnomis@gmail.com">gmbnomis@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I apologize for the lengthy post, but I did not know where to file an issue for<br>
this. It is a generic problem affecting most Pulp 3 plugins.<br>
<br>
I am puzzled for some time now about the natural keys used for content in<br>
plugins. Examples are:<br>
<br>
pulp_python: 'filename'<br>
pulp_ansible: 'version', 'role' (for role: 'namespace', 'name')<br>
pulp_rpm (RPM package): 'name', 'epoch', 'version', 'release', 'arch', 'checksum_type', 'pkgId'<br>
pulp_cookbook:  'name', 'version'<br>
<br>
These look like keys that make sense for content in a single repo (version), but<br>
not necessarily for content in a per plugin pool of content. In an ideal world,<br>
these keys are globally unique, i.e. there is only a single "utils-0.9.0" Python<br>
module world-wide that refers to the same artifacts as the "utils-0.9.0" module on<br>
PyPi. But, as far as I know, the world is far from ideal, especially in an<br>
enterprise setting...<br></blockquote><div>Agreed. This uniqueness is what allows Pulp to recognize and deduplicate content in its database. On the filesystem the content addressable storage will store identical assets only once, but if Pulp couldn't recognize "utils-0.9.0" from one repo as the same as "utils-0.9.0" then each sync/upload makes all new content units each time.<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
With the current implementation, the following scenarios could happen if I got<br>
it right:<br>
<br>
1. In Acme Corp, a team develops a Python module/Ansible role/Chef cookbook<br>
   called "acme_utils", which is part of a repo on a Pulp instance. Another team<br>
   using different repos happens to choose the same name for their unrelated<br>
   utility package. They may not be able to create a content unit if they use<br>
   e.g. the same version or file name.<br></blockquote><div>I agree this is an issue<br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
2. A team happens to choose a name that is already known in<br>
   PyPi/Galaxy/Supermarket. (Or, someone posts a new name on<br>
   PyPi/Galaxy/Supermarket that happens to be in use in the company for years).<br>
   Then, as above, the team may not be able to create content units for their<br>
   own artifacts.<br></blockquote><div>I agree this is an issue</div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
   Additionally, *very ugly* things may happen during a sync. The current<br>
   QueryExistingContentUnits stage may decide that, based on the natural key,<br>
   completely unrelated content units are already present. The stage just puts<br>
   them into the new repo version. </blockquote><div>I agree this is an issue</div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
   Example for pulp_python:<br>
<br>
   Somebody does something very stupid (or very sinister):<br>
<br>
   (The files "Django-1.11.16-py2.py3-none-any.whl" and "Django-1.11.16.tar.gz" need<br>
   to be in the current directory.)<br>
<br>
export ARTIFACT_HREF=$(http --form POST :8000/pulp/api/v3/artifacts/ file@./Django-1.11.16-py2.py3-none-any.whl | jq -r '._href')<br>
http POST :8000/pulp/api/v3/content/python/packages/ artifact=$ARTIFACT_HREF filename=Django-2.0-py3-none-any.whl<br>
export ARTIFACT_HREF=$(http --form POST :8000/pulp/api/v3/artifacts/ file@./Django-1.11.16.tar.gz | jq -r '._href')<br>
http POST :8000/pulp/api/v3/content/python/packages/ artifact=$ARTIFACT_HREF filename=Django-2.0.tar.gz<br></blockquote><div>Yes, this is a problem, and here are some related thoughts. Core provides these generic CRUD urls so that plugin writers could get away with never writing a "one-shot" viewset that receives and parses a content unit via upload in one call. Using "one-shot" uploaders stops receiving untrusted metadata from the user (as in your example), but unless the units coming in are also signed with a trusted key, the data of the file being uploaded could have been altered. Also the same user likely configured that trusted key.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
   Somebody else wants to mirror Django 2.0 from PyPi (version_specifier: "==2.0"):<br></blockquote><div>I think you've gotten to the crux of the issue here ... "someone else". Pulp is not currently able to handle real multi-tenancy. A multi-tenancy system would isolate each users content or provide access to content via RBAC. We have gotten requests for multi-tenancy from several users who list it as a must-have. I want to connect this "user-to-user" sharing problem as actually a multi-tenancy problem.<br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
http POST :8000/pulp/api/v3/repositories/ name=foo<br>
export REPO_HREF=$(http :8000/pulp/api/v3/repositories/ | jq -r '.results[] | select(.name == "foo") | ._href')<br>
http -v POST :8000/pulp/api/v3/remotes/python/  name='bar'  url='<a href="https://pypi.org/" rel="noreferrer" target="_blank">https://pypi.org/</a>' 'includes:=[{"name": "django", "version_specifier":"==2.0"}]'<br>
export REMOTE_HREF=$(http :8000/pulp/api/v3/remotes/python/ | jq -r '.results[] | select(.name == "bar") | ._href')<br>
http POST :8000$REMOTE_HREF'sync/' repository=$REPO_HREF<br>
<br>
   Now the created repo version contains bogus content (Django 1.11.16 instead of 2.0):<br>
<br>
$ http :8000/pulp/api/v3/repositories/1/versions/1/content/ | jq '.["results"] | map(.version, .artifact)'<br>
[<br>
  "1.11.16",<br>
  "/pulp/api/v3/artifacts/1/",<br>
  "1.11.16",<br>
  "/pulp/api/v3/artifacts/2/"<br>
]<br>
<br>
   A "not so dumb" version of this scenario may happen by error like this:<br>
<br>
export ARTIFACT_HREF=$(http --form POST :8000/pulp/api/v3/artifacts/ file@./Django-1.11.15-py2.py3-none-any.whl | jq -r '._href')<br>
http POST :8000/pulp/api/v3/content/python/packages/ artifact=$ARTIFACT_HREF filename=Django-1.11.15-py2.py3-none-any.whl<br>
#Forgot to do this: export ARTIFACT_HREF=$(http --form POST :8000/pulp/api/v3/artifacts/ file@./Django-1.11.16-py2.py3-none-any.whl | jq -r '._href')<br>
http POST :8000/pulp/api/v3/content/python/packages/ artifact=$ARTIFACT_HREF filename=Django-1.11.16-py2.py3-none-any.whl<br>
<br>
   From now on, no synced repo version on the same Pulp instance will have a<br>
   Django 1.11.16 wheel.<br></blockquote><div>Similar observation here. If Pulp were a multi-tenant system, only that 1 user would have the screwed up content.</div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
3. A team releases "module" version "2.0.0" by creating a new version of the<br>
   "release" repo. However, packaging went wrong and the release needs to be<br>
   rebuilt. Nobody wants to use version "2.0.1" for the new shiny release, it<br>
   must be "2.0.0" (the version hasn't been published to the outside world yet).<br>
   How does the team publish a new repo version containing the re-released<br>
   module? (The best idea I have is: the team needs to create a new version<br>
   without the content unit first. Then, find _all_ repo versions that still<br>
   reference the content unit and delete them. Delete orphan content units.<br>
   Create the new content unit and add it to a new repo version).<br></blockquote><div>Yes this is the same process we imagined users would go through. If version "2.0.0" is stored in multiple repos or repo versions to fully remove the bad one its unavoidable to unassociate from all repos and then orphan cleanup. This process is also motivated by the use case I call "get this unit out of here" which is a situation like shellshock where: "we know this unit has a CVE in it, it's not safe to store in Pulp anymore". In this area I can't think of a better way since removing-republishing a unit in a fully-automated way could have significant unexpected consequences on published content. It's probably do-able but we would need to be careful.<br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
4. A Pulp instance contains unsigned RPM content that will be signed for<br>
   release. It is not possible to store the signed RPMs on the same instance.<br>
   (Or alternatively, someone just forgot to sign the RPMs when<br>
   importing/syncing. They will remain unsigned on subsequent syncs even if the<br>
   remote repo has been fixed.)</blockquote><div>I agree this is an issue, and we absolutely need to support the workflow.<br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
(I did not check the behavior in Pulp 2, but most content types have fields like<br>
checksum/commit/repo_id/digest in their unit key.)<br>
<br>
Before discussing implementation options (changing key, adapt sync), I have the<br>
following questions:<br>
<br>
- Is the assessment of the scenarios outlined above correct?<br></blockquote><div>Yes. The thing to keep in mind through all this is that Pulp needs to compose repos which when presented to a client, e.g. pip, dnf, etc don't contain the same package twice. So in many ways the uniqueness is about playing that game up front during upload/sync and not on the backend during publish time. If it's important to do then doing it early I think is key.<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
- Do you think it make sense to support (some of) these use cases?<br></blockquote><div>Yes </div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
- If so, are there plans to do so that I am not aware of?<br></blockquote><div>No, except that I believe we need to consider multi-tenancy as a possible solution. There are no plans or discussion on that yet (until now!).<br></div><div>I hope the plugin API post GA introduces some signing feature allowing users to integrate pulp w/ local and message based signing options. This is related to your RPM signing point above</div><div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
_______________________________________________<br>
Pulp-dev mailing list<br>
<a href="mailto:Pulp-dev@redhat.com" target="_blank">Pulp-dev@redhat.com</a><br>
<a href="https://www.redhat.com/mailman/listinfo/pulp-dev" rel="noreferrer" target="_blank">https://www.redhat.com/mailman/listinfo/pulp-dev</a><br>
</blockquote></div></div>