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: 1 addition & 1 deletion .github/workflows/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ if [ "$TEST" = "azure" ]; then
- ./azurite:/etc/pulp\
command: "azurite-blob --blobHost 0.0.0.0"' vars/main.yaml
sed -i -e '$a azure_test: true\
pulp_scenario_settings: {"flatpak_index": true}\
pulp_scenario_settings: {"domain_enabled": true, "flatpak_index": true}\
pulp_scenario_env: {}\
' vars/main.yaml
fi
Expand Down
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 it
only functions within the default domain.
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
41 changes: 28 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 @@ -231,6 +232,7 @@ def has_permission(self, obj, method, action, data):
request.method = method
request.user = self.user
request._full_data = data
request.pulp_domain = get_domain()
# Fake the corresponding view
view = FakeViewWithSerializer(action, lambda: obj)
return self.access_policy.has_permission(request, view)
Expand All @@ -239,50 +241,63 @@ 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):
if pt_distribution := get_pull_through_distribution(path, domain):
# 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 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):
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
13 changes: 8 additions & 5 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,23 +66,25 @@ def find_base_path_cached(request, cached):

"""
path = request.resolver_match.kwargs["path"]
path_exists = cached.exists(base_key=path)
base_key = cache_key(path)
path_exists = cached.exists(base_key=base_key)
if path_exists:
return path
return base_key
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()
)
if not distro:
raise RepositoryNotFound(name=path)

return distro.base_path
return cache_key(distro.base_path)


class FlatpakIndexStaticCache(SyncContentCache):
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)])
28 changes: 21 additions & 7 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 Down Expand Up @@ -44,23 +48,31 @@ def has_namespace_perms(request, view, action, permission):
return False
namespace = base_path.split("/")[0]
try:
namespace = models.ContainerNamespace.objects.get(name=namespace)
namespace = models.ContainerNamespace.objects.get(
name=namespace, pulp_domain=request.pulp_domain
)
except models.ContainerNamespace.DoesNotExist:
return False
else:
return request.user.has_perm(permission) or request.user.has_perm(ns_perm, namespace)
return (
request.user.has_perm(permission)
or request.user.has_perm(permission, request.pulp_domain)
or request.user.has_perm(ns_perm, namespace)
)


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)
or request.user.has_perm(permission, request.pulp_domain)
or request.user.has_perm(permission, view.get_object())
)


Expand Down Expand Up @@ -99,13 +111,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')},
),
]
Loading
Loading