Revert "*: providers and sources -> channels, PolicyModel to PolicyBindingModel that uses custom M2M through"
This reverts commit 7ed3ceb960.
This commit is contained in:
0
passbook/providers/app_gw/__init__.py
Normal file
0
passbook/providers/app_gw/__init__.py
Normal file
45
passbook/providers/app_gw/api.py
Normal file
45
passbook/providers/app_gw/api.py
Normal 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
|
||||
11
passbook/providers/app_gw/apps.py
Normal file
11
passbook/providers/app_gw/apps.py
Normal 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/"
|
||||
40
passbook/providers/app_gw/forms.py
Normal file
40
passbook/providers/app_gw/forms.py
Normal 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(),
|
||||
}
|
||||
99
passbook/providers/app_gw/migrations/0001_initial.py
Normal file
99
passbook/providers/app_gw/migrations/0001_initial.py
Normal 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",),
|
||||
),
|
||||
]
|
||||
@ -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",),
|
||||
]
|
||||
@ -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",),
|
||||
),
|
||||
]
|
||||
@ -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,
|
||||
),
|
||||
]
|
||||
0
passbook/providers/app_gw/migrations/__init__.py
Normal file
0
passbook/providers/app_gw/migrations/__init__.py
Normal file
44
passbook/providers/app_gw/models.py
Normal file
44
passbook/providers/app_gw/models.py
Normal 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")
|
||||
0
passbook/providers/app_gw/provider/__init__.py
Normal file
0
passbook/providers/app_gw/provider/__init__.py
Normal 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 }}
|
||||
64
passbook/providers/app_gw/templates/app_gw/k8s-manifest.yaml
Normal file
64
passbook/providers/app_gw/templates/app_gw/k8s-manifest.yaml
Normal 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
|
||||
57
passbook/providers/app_gw/templates/app_gw/setup_modal.html
Normal file
57
passbook/providers/app_gw/templates/app_gw/setup_modal.html
Normal 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>
|
||||
10
passbook/providers/app_gw/urls.py
Normal file
10
passbook/providers/app_gw/urls.py
Normal 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"
|
||||
),
|
||||
]
|
||||
40
passbook/providers/app_gw/views.py
Normal file
40
passbook/providers/app_gw/views.py
Normal 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",
|
||||
)
|
||||
Reference in New Issue
Block a user