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

Add initial support for domains #1829

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES/domain-enablement.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add partial support for Domains. The plugin can be installed with the feature turned on, but only
the default domain will properly work.
Comment on lines +1 to +2
Copy link
Member

Choose a reason for hiding this comment

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

I would much prefer if we can say "it only functions in the default domain". By that i mean, that you could not create a container repository outside of the default domain for example.

1 change: 1 addition & 0 deletions pulp_container/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class PulpContainerPluginAppConfig(PulpPluginAppConfig):
label = "container"
version = "2.23.0.dev"
python_package_name = "pulp-container"
domain_compatible = True

def ready(self):
super().ready()
Expand Down
42 changes: 29 additions & 13 deletions pulp_container/app/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization

from pulpcore.plugin.util import get_domain
from pulp_container.app.models import (
ContainerDistribution,
ContainerNamespace,
Expand Down Expand Up @@ -209,10 +210,10 @@ def generate_claim_set(issuer, issued_at, subject, audience, access):
}


def get_pull_through_distribution(path):
def get_pull_through_distribution(path, domain):
return (
ContainerPullThroughDistribution.objects.annotate(path=Value(path))
.filter(path__startswith=F("base_path"))
.filter(pulp_domain=domain, path__startswith=F("base_path"))
.order_by("-base_path")
.first()
)
Expand All @@ -239,50 +240,65 @@ def has_pull_permissions(self, path):
"""
Check if the user has permissions to pull from the repository specified by the path.
"""
domain = get_domain()
try:
distribution = ContainerDistribution.objects.get(base_path=path)
distribution = ContainerDistribution.objects.get(base_path=path, pulp_domain=domain)
except ContainerDistribution.DoesNotExist:
namespace_name = path.split("/")[0]
try:
namespace = ContainerNamespace.objects.get(name=namespace_name)
namespace = ContainerNamespace.objects.get(name=namespace_name, pulp_domain=domain)
except ContainerNamespace.DoesNotExist:
# Check if the user is allowed to create a new namespace
return self.has_permission(None, "POST", "create", {"name": namespace_name})
return self.has_permission(
None, "POST", "create", {"name": namespace_name, "pulp_domain": domain}
)

if pt_distribution := get_pull_through_distribution(path):
# Check if the user is allowed to create a new distribution
return self.has_pull_through_new_distribution_permissions(pt_distribution)
else:
# Check if the user is allowed to view distributions in the namespace
return self.has_permission(
namespace, "GET", "view_distribution", {"name": namespace_name}
namespace,
"GET",
"view_distribution",
{"name": namespace_name, "pulp_domain": domain},
)

if pt_distribution := get_pull_through_distribution(path):
if pt_distribution := get_pull_through_distribution(path, domain):
# Check if the user is allowed to pull new content via a pull-through distribution
if self.has_pull_through_permissions(distribution):
if self.has_pull_through_permissions(
pt_distribution
): # was this using the wrong variable?
return True

# Check if the user has general pull permissions
return self.has_permission(distribution, "GET", "pull", {"base_path": path})
return self.has_permission(
distribution, "GET", "pull", {"base_path": path, "pulp_domain": domain}
)

def has_push_permissions(self, path):
"""
Check if the user has permissions to push to the repository specified by the path.
"""
domain = get_domain()
try:
distribution = ContainerDistribution.objects.get(base_path=path)
distribution = ContainerDistribution.objects.get(base_path=path, pulp_domain=domain)
except ContainerDistribution.DoesNotExist:
namespace_name = path.split("/")[0]
try:
namespace = ContainerNamespace.objects.get(name=namespace_name)
namespace = ContainerNamespace.objects.get(name=namespace_name, pulp_domain=domain)
except ContainerNamespace.DoesNotExist:
# Check if user is allowed to create a new namespace
return self.has_permission(None, "POST", "create", {"name": namespace_name})
return self.has_permission(
None, "POST", "create", {"name": namespace_name, "pulp_domain": domain}
)
# Check if user is allowed to create a new distribution in the namespace
return self.has_permission(namespace, "POST", "create_distribution", {})

return self.has_permission(distribution, "POST", "push", {"base_path": path})
return self.has_permission(
distribution, "POST", "push", {"base_path": path, "pulp_domain": domain}
)

def has_view_catalog_permissions(self, path):
"""
Expand Down
8 changes: 5 additions & 3 deletions pulp_container/app/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db.models import F, Value

from pulpcore.plugin.cache import CacheKeys, AsyncContentCache, SyncContentCache
from pulpcore.plugin.util import get_domain, cache_key

from pulp_container.app.models import ContainerDistribution, ContainerPullThroughDistribution
from pulp_container.app.exceptions import RepositoryNotFound
Expand Down Expand Up @@ -65,16 +66,17 @@ def find_base_path_cached(request, cached):

"""
path = request.resolver_match.kwargs["path"]
path_exists = cached.exists(base_key=path)
path_exists = cached.exists(base_key=cache_key(path))
if path_exists:
return path
else:
domain = get_domain()
try:
distro = ContainerDistribution.objects.get(base_path=path)
distro = ContainerDistribution.objects.get(base_path=path, pulp_domain=domain)
except ObjectDoesNotExist:
distro = (
ContainerPullThroughDistribution.objects.annotate(path=Value(path))
.filter(path__startswith=F("base_path"))
.filter(path__startswith=F("base_path"), pulp_domain=domain)
.order_by("-base_path")
.first()
)
Expand Down
7 changes: 5 additions & 2 deletions pulp_container/app/content.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
from aiohttp import web
from django.conf import settings

from pulpcore.plugin.content import app
from pulp_container.app.registry import Registry

registry = Registry()

PREFIX = "/pulp/container/{pulp_domain}/" if settings.DOMAIN_ENABLED else "/pulp/container/"

app.add_routes(
[
web.get(
r"/pulp/container/{path:.+}/{content:(blobs|manifests)}/sha256:{digest:.+}",
PREFIX + r"{path:.+}/{content:(blobs|manifests)}/sha256:{digest:.+}",
registry.get_by_digest,
)
]
)
app.add_routes([web.get(r"/pulp/container/{path:.+}/manifests/{tag_name}", registry.get_tag)])
app.add_routes([web.get(PREFIX + r"{path:.+}/manifests/{tag_name}", registry.get_tag)])
24 changes: 11 additions & 13 deletions pulp_container/app/global_access_conditions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from logging import getLogger
from django.conf import settings

from pulpcore.plugin.models import Repository
from pulpcore.plugin.viewsets import RepositoryVersionViewSet
Expand All @@ -12,10 +13,13 @@
def has_namespace_obj_perms(request, view, action, permission):
"""
Check if a user has object-level perms on the namespace associated with the distribution
or repository.
or repository. If they have model/domain level permission then return True.
"""
if request.user.has_perm(permission):
return True
if settings.DOMAIN_ENABLED:
if request.user.has_perm(permission, request.pulp_domain):
return True
if isinstance(view, RepositoryVersionViewSet):
obj = Repository.objects.get(pk=view.kwargs["repository_pk"]).cast()
else:
Expand All @@ -42,26 +46,18 @@ def has_namespace_perms(request, view, action, permission):
base_path = request.data.get("base_path")
if not base_path:
return False
namespace = base_path.split("/")[0]
try:
namespace = models.ContainerNamespace.objects.get(name=namespace)
except models.ContainerNamespace.DoesNotExist:
return False
else:
return request.user.has_perm(permission) or request.user.has_perm(ns_perm, namespace)
return has_namespace_obj_perms(request, view, action, ns_perm)


def has_namespace_or_obj_perms(request, view, action, permission):
"""
Check if a user has a namespace-level perms or object-level permission
Check if a user has a namespace-level perms or permissions on the original object
"""
ns_perm = "container.namespace_{}".format(permission.split(".", 1)[1])
if has_namespace_obj_perms(request, view, action, ns_perm):
return True
else:
return request.user.has_perm(permission) or request.user.has_perm(
permission, view.get_object()
)
return request.user.has_perm(permission, view.get_object())


def obj_exists(request, view, action):
Expand Down Expand Up @@ -99,13 +95,15 @@ def has_namespace_model_perms(request, view, action):
"""
if request.user.has_perm("container.add_containernamespace"):
return True
if settings.DOMAIN_ENABLED:
return request.user.has_perm("container.add_containernamespace", obj=request.pulp_domain)
return False


def has_distribution_perms(request, view, action, permission):
"""
Check if the user has permissions on the corresponding distribution.
Model or object permission is sufficient.
Model, domain or object permission is sufficient.
"""
if request.user.has_perm(permission):
return True
Expand Down
81 changes: 81 additions & 0 deletions pulp_container/app/migrations/0044_add_domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Generated by Django 4.2.16 on 2024-11-21 20:59

from django.db import migrations, models
import django.db.models.deletion
import pulpcore.app.util


class Migration(migrations.Migration):

dependencies = [
('core', '0125_openpgpdistribution_openpgpkeyring_openpgppublickey_and_more'),
('container', '0043_add_os_arch_image_size_manifest_fields'),
]

operations = [
migrations.AlterUniqueTogether(
name='blob',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='containernamespace',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='manifest',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='manifestsignature',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='tag',
unique_together=set(),
),
migrations.AddField(
model_name='blob',
name='_pulp_domain',
field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'),
),
migrations.AddField(
model_name='containernamespace',
name='pulp_domain',
field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'),
),
migrations.AddField(
model_name='manifest',
name='_pulp_domain',
field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'),
),
migrations.AddField(
model_name='manifestsignature',
name='_pulp_domain',
field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'),
),
migrations.AddField(
model_name='tag',
name='_pulp_domain',
field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, to='core.domain'),
),
migrations.AlterUniqueTogether(
name='blob',
unique_together={('digest', '_pulp_domain')},
),
migrations.AlterUniqueTogether(
name='containernamespace',
unique_together={('name', 'pulp_domain')},
),
migrations.AlterUniqueTogether(
name='manifest',
unique_together={('digest', '_pulp_domain')},
),
migrations.AlterUniqueTogether(
name='manifestsignature',
unique_together={('digest', '_pulp_domain')},
),
migrations.AlterUniqueTogether(
name='tag',
unique_together={('name', 'tagged_manifest', '_pulp_domain')},
),
]
17 changes: 11 additions & 6 deletions pulp_container/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
Upload as CoreUpload,
)
from pulpcore.plugin.repo_version_utils import remove_duplicates, validate_repo_version
from pulpcore.plugin.util import gpg_verify
from pulpcore.plugin.util import gpg_verify, get_domain_pk


from . import downloaders
Expand Down Expand Up @@ -63,10 +63,11 @@ class Blob(Content):
TYPE = "blob"

digest = models.TextField(db_index=True)
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = ("digest",)
unique_together = ("digest", "_pulp_domain")


class Manifest(Content):
Expand Down Expand Up @@ -138,6 +139,7 @@ class Manifest(Content):
symmetrical=False,
through_fields=("image_manifest", "manifest_list"),
)
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)

def __init__(self, *args, **kwargs):
self._json_manifest = None
Expand Down Expand Up @@ -302,7 +304,7 @@ def is_artifact(self):

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = ("digest",)
unique_together = ("digest", "_pulp_domain")


class BlobManifest(models.Model):
Expand Down Expand Up @@ -381,10 +383,11 @@ class Tag(Content):
tagged_manifest = models.ForeignKey(
Manifest, null=False, related_name="tagged_manifests", on_delete=models.CASCADE
)
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = (("name", "tagged_manifest"),)
unique_together = ("name", "tagged_manifest", "_pulp_domain")


class ManifestSignature(Content):
Expand Down Expand Up @@ -421,12 +424,13 @@ class ManifestSignature(Content):
signed_manifest = models.ForeignKey(
Manifest, null=False, related_name="signed_manifests", on_delete=models.CASCADE
)
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)
# TODO: Maybe there should be an optional field with a FK to a signing_service for the cases
# when Pulp creates a signature.

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = (("digest",),)
unique_together = ("digest", "_pulp_domain")


class ContainerNamespace(BaseModel, AutoAddObjPermsMixin):
Expand All @@ -438,9 +442,10 @@ class ContainerNamespace(BaseModel, AutoAddObjPermsMixin):
"""

name = models.TextField(db_index=True)
pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)

class Meta:
unique_together = (("name",),)
unique_together = ("name", "pulp_domain")
permissions = [
("namespace_add_containerdistribution", "Add any distribution to a namespace"),
("namespace_delete_containerdistribution", "Delete any distribution from a namespace"),
Expand Down
Loading
Loading