Skip to content

Commit

Permalink
Add initial support for domains
Browse files Browse the repository at this point in the history
  • Loading branch information
gerrod3 committed Nov 21, 2024
1 parent c2352bf commit 9d173eb
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 150 deletions.
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.
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

0 comments on commit 9d173eb

Please sign in to comment.