Revert "*: providers and sources -> channels, PolicyModel to PolicyBindingModel that uses custom M2M through"

This reverts commit 7ed3ceb960.
This commit is contained in:
Jens Langhammer
2020-05-16 16:02:42 +02:00
parent 7ed3ceb960
commit 406f69080b
293 changed files with 4692 additions and 3244 deletions

View File

View File

@ -0,0 +1,45 @@
"""ApplicationGatewayProvider API Views"""
from oauth2_provider.generators import generate_client_id, generate_client_secret
from oidc_provider.models import Client
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSet
from passbook.providers.app_gw.models import ApplicationGatewayProvider
from passbook.providers.oidc.api import OpenIDProviderSerializer
class ApplicationGatewayProviderSerializer(ModelSerializer):
"""ApplicationGatewayProvider Serializer"""
client = OpenIDProviderSerializer()
def create(self, validated_data):
instance = super().create(validated_data)
instance.client = Client.objects.create(
client_id=generate_client_id(), client_secret=generate_client_secret()
)
instance.save()
return instance
def update(self, instance, validated_data):
self.instance.client.name = self.instance.name
self.instance.client.redirect_uris = [
f"http://{self.instance.host}/oauth2/callback",
f"https://{self.instance.host}/oauth2/callback",
]
self.instance.client.scope = ["openid", "email"]
self.instance.client.save()
return super().update(instance, validated_data)
class Meta:
model = ApplicationGatewayProvider
fields = ["pk", "name", "internal_host", "external_host", "client"]
read_only_fields = ["client"]
class ApplicationGatewayProviderViewSet(ModelViewSet):
"""ApplicationGatewayProvider Viewset"""
queryset = ApplicationGatewayProvider.objects.all()
serializer_class = ApplicationGatewayProviderSerializer

View File

@ -0,0 +1,11 @@
"""passbook Application Security Gateway app"""
from django.apps import AppConfig
class PassbookApplicationApplicationGatewayConfig(AppConfig):
"""passbook app_gw app"""
name = "passbook.providers.app_gw"
label = "passbook_providers_app_gw"
verbose_name = "passbook Providers.Application Security Gateway"
mountpoint = "application/gateway/"

View File

@ -0,0 +1,40 @@
"""passbook Application Security Gateway Forms"""
from django import forms
from oauth2_provider.generators import generate_client_id, generate_client_secret
from oidc_provider.models import Client, ResponseType
from passbook.providers.app_gw.models import ApplicationGatewayProvider
class ApplicationGatewayProviderForm(forms.ModelForm):
"""Security Gateway Provider form"""
def save(self, *args, **kwargs):
if not self.instance.pk:
# New instance, so we create a new OIDC client with random keys
self.instance.client = Client.objects.create(
client_id=generate_client_id(), client_secret=generate_client_secret()
)
self.instance.client.name = self.instance.name
self.instance.client.response_types.set(
[ResponseType.objects.get_by_natural_key("code")]
)
self.instance.client.redirect_uris = [
f"http://{self.instance.external_host}/oauth2/callback",
f"https://{self.instance.external_host}/oauth2/callback",
f"http://{self.instance.internal_host}/oauth2/callback",
f"https://{self.instance.internal_host}/oauth2/callback",
]
self.instance.client.scope = ["openid", "email"]
self.instance.client.save()
return super().save(*args, **kwargs)
class Meta:
model = ApplicationGatewayProvider
fields = ["name", "internal_host", "external_host"]
widgets = {
"name": forms.TextInput(),
"internal_host": forms.TextInput(),
"external_host": forms.TextInput(),
}

View File

@ -0,0 +1,99 @@
# Generated by Django 2.2.6 on 2019-10-07 14:07
import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("passbook_core", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="ApplicationGatewayProvider",
fields=[
(
"provider_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.Provider",
),
),
(
"server_name",
django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(), size=None
),
),
(
"upstream",
django.contrib.postgres.fields.ArrayField(
base_field=models.TextField(), size=None
),
),
("enabled", models.BooleanField(default=True)),
(
"authentication_header",
models.TextField(blank=True, default="X-Remote-User"),
),
(
"default_content_type",
models.TextField(default="application/octet-stream"),
),
("upstream_ssl_verification", models.BooleanField(default=True)),
],
options={
"verbose_name": "Application Gateway Provider",
"verbose_name_plural": "Application Gateway Providers",
},
bases=("passbook_core.provider",),
),
migrations.CreateModel(
name="RewriteRule",
fields=[
(
"propertymapping_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.PropertyMapping",
),
),
("match", models.TextField()),
("halt", models.BooleanField(default=False)),
("replacement", models.TextField()),
(
"redirect",
models.CharField(
choices=[
("internal", "Internal"),
(301, "Moved Permanently"),
(302, "Found"),
],
max_length=50,
),
),
(
"conditions",
models.ManyToManyField(blank=True, to="passbook_core.Policy"),
),
],
options={
"verbose_name": "Rewrite Rule",
"verbose_name_plural": "Rewrite Rules",
},
bases=("passbook_core.propertymapping",),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-11-11 17:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0005_merge_20191025_2022"),
("passbook_providers_app_gw", "0001_initial"),
]
operations = [
migrations.RemoveField(model_name="rewriterule", name="conditions",),
migrations.RemoveField(model_name="rewriterule", name="propertymapping_ptr",),
migrations.DeleteModel(name="ApplicationGatewayProvider",),
migrations.DeleteModel(name="RewriteRule",),
]

View File

@ -0,0 +1,48 @@
# Generated by Django 2.2.7 on 2019-11-11 17:08
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("passbook_core", "0005_merge_20191025_2022"),
("oidc_provider", "0026_client_multiple_response_types"),
("passbook_providers_app_gw", "0002_auto_20191111_1703"),
]
operations = [
migrations.CreateModel(
name="ApplicationGatewayProvider",
fields=[
(
"provider_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.Provider",
),
),
("name", models.TextField()),
("host", models.TextField()),
(
"client",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="oidc_provider.Client",
),
),
],
options={
"verbose_name": "Application Gateway Provider",
"verbose_name_plural": "Application Gateway Providers",
},
bases=("passbook_core.provider",),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 2.2.9 on 2020-01-02 15:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_providers_app_gw", "0003_applicationgatewayprovider"),
]
operations = [
migrations.RenameField(
model_name="applicationgatewayprovider",
old_name="host",
new_name="external_host",
),
migrations.AddField(
model_name="applicationgatewayprovider",
name="internal_host",
field=models.TextField(default=""),
preserve_default=False,
),
]

View File

@ -0,0 +1,44 @@
"""passbook app_gw models"""
import string
from random import SystemRandom
from typing import Optional
from django.db import models
from django.http import HttpRequest
from django.utils.translation import gettext as _
from oidc_provider.models import Client
from passbook import __version__
from passbook.core.models import Provider
from passbook.lib.utils.template import render_to_string
class ApplicationGatewayProvider(Provider):
"""This provider uses oauth2_proxy with the OIDC Provider."""
name = models.TextField()
internal_host = models.TextField()
external_host = models.TextField()
client = models.ForeignKey(Client, on_delete=models.CASCADE)
form = "passbook.providers.app_gw.forms.ApplicationGatewayProviderForm"
def html_setup_urls(self, request: HttpRequest) -> Optional[str]:
"""return template and context modal with URLs for authorize, token, openid-config, etc"""
cookie_secret = "".join(
SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(50)
)
return render_to_string(
"app_gw/setup_modal.html",
{"provider": self, "cookie_secret": cookie_secret, "version": __version__},
)
def __str__(self):
return f"Application Gateway {self.name}"
class Meta:
verbose_name = _("Application Gateway Provider")
verbose_name_plural = _("Application Gateway Providers")

View File

@ -0,0 +1,15 @@
version: "3.5"
services:
passbook_gatekeeper:
container_name: gatekeeper
image: beryju/passbook-gatekeeper:{{ version }}
ports:
- 4180:4180
environment:
OAUTH2_PROXY_CLIENT_ID: {{ provider.client.client_id }}
OAUTH2_PROXY_CLIENT_SECRET: {{ provider.client.client_secret }}
OAUTH2_PROXY_REDIRECT_URL: https://{{ provider.external_host }}/oauth2/callback
OAUTH2_PROXY_OIDC_ISSUER_URL: https://{{ request.META.HTTP_HOST }}/application/oidc
OAUTH2_PROXY_COOKIE_SECRET: {{ cookie_secret }}
OAUTH2_PROXY_UPSTREAMS: http://{{ provider.internal_host }}

View File

@ -0,0 +1,64 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: passbook-gatekeeper
name: passbook-gatekeeper
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
k8s-app: passbook-gatekeeper
template:
metadata:
labels:
k8s-app: passbook-gatekeeper
spec:
containers:
- args:
- --upstream=file:///dev/null
env:
- name: OAUTH2_PROXY_CLIENT_ID
value: {{ provider.client.client_id }}
- name: OAUTH2_PROXY_CLIENT_SECRET
value: {{ provider.client.client_secret }}
- name: OAUTH2_PROXY_COOKIE_SECRET
value: {{ cookie_secret }}
image: beryju/passbook-gatekeeper:{{ version }}
imagePullPolicy: Always
name: passbook-gatekeeper
ports:
- containerPort: 4180
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: passbook-gatekeeper
name: passbook-gatekeeper
namespace: kube-system
spec:
ports:
- name: http
port: 4180
protocol: TCP
targetPort: 4180
selector:
k8s-app: passbook-gatekeeper
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: passbook-gatekeeper
namespace: kube-system
spec:
rules:
- host: {{ provider.external_host }}
http:
paths:
- backend:
serviceName: passbook-gatekeeper
servicePort: 4180
path: /oauth2

View File

@ -0,0 +1,57 @@
{% load i18n %}
{% load static %}
<div class="pf-c-dropdown">
<button class="pf-c-button pf-m-tertiary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Setup with...' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
<li>
<button class="pf-c-dropdown__menu-item" data-target="modal" data-modal="docker-compose-{{ provider.pk }}">{% trans 'docker-compose' %}</button>
</li>
<li>
<button class="pf-c-dropdown__menu-item" data-target="modal" data-modal="k8s-{{ provider.pk }}">{% trans 'Kubernetes' %}</button>
</li>
</ul>
</div>
<div class="pf-c-backdrop" id="docker-compose-{{ provider.pk }}" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-lg" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with docker-compose' %}</h1>
<div class="pf-c-modal-box__body">
{% trans 'Add the following snippet to your docker-compose file.' %}
<textarea class="codemirror" readonly data-cm-mode="yaml">{% include 'app_gw/docker-compose.yml' %}</textarea>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button data-modal-close class="pf-c-button pf-m-primary" type="button">{% trans 'Close' %}</button>
</footer>
</div>
</div>
</div>
<div class="pf-c-backdrop" id="k8s-{{ provider.pk }}" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-lg" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
<h1 class="pf-c-title pf-m-2xl">{% trans 'Setup with Kubernetes' %}</h1>
<div class="pf-c-modal-box__body">
<p>{% trans 'Download the manifest to create the Gatekeeper deployment and service:' %}</p>
<a href="{% url 'passbook_providers_app_gw:k8s-manifest' provider=provider.pk %}">{% trans 'Here' %}</a>
<p>{% trans 'Afterwards, add the following annotations to the Ingress you want to secure:' %}</p>
<textarea class="codemirror" readonly data-cm-mode="yaml">
nginx.ingress.kubernetes.io/auth-url: "https://{{ provider.external_host }}/oauth2/auth"
nginx.ingress.kubernetes.io/auth-signin: "https://{{ provider.external_host }}/oauth2/start?rd=$escaped_request_uri"
</textarea>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button data-modal-close class="pf-c-button pf-m-primary" type="button">{% trans 'Close' %}</button>
</footer>
</div>
</div>
</div>

View File

@ -0,0 +1,10 @@
"""passbook app_gw urls"""
from django.urls import path
from passbook.providers.app_gw.views import K8sManifestView
urlpatterns = [
path(
"<int:provider>/k8s-manifest/", K8sManifestView.as_view(), name="k8s-manifest"
),
]

View File

@ -0,0 +1,40 @@
"""passbook app_gw views"""
import string
from random import SystemRandom
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render
from django.views import View
from structlog import get_logger
from passbook import __version__
from passbook.providers.app_gw.models import ApplicationGatewayProvider
ORIGINAL_URL = "HTTP_X_ORIGINAL_URL"
LOGGER = get_logger()
def get_cookie_secret():
"""Generate random 50-character string for cookie-secret"""
return "".join(
SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(50)
)
class K8sManifestView(LoginRequiredMixin, View):
"""Generate K8s Deployment and SVC for gatekeeper"""
def get(self, request: HttpRequest, provider: int) -> HttpResponse:
"""Render deployment template"""
provider = get_object_or_404(ApplicationGatewayProvider, pk=provider)
return render(
request,
"app_gw/k8s-manifest.yaml",
{
"provider": provider,
"cookie_secret": get_cookie_secret(),
"version": __version__,
},
content_type="text/yaml",
)