Compare commits

...

28 Commits

Author SHA1 Message Date
f124314eab new release: 0.7.12-beta 2020-01-02 20:22:44 +01:00
684e4ffdcf providers/app_gw: fix formatting 2020-01-02 20:22:36 +01:00
d9ff5c69c8 providers/app_gw: fix assignment of response_types 2020-01-02 20:20:10 +01:00
8142e3df45 providers/oidc: fix application property of wrong object being used 2020-01-02 20:19:53 +01:00
73920899de static: use current pixie image 2020-01-02 20:09:30 +01:00
13666965a7 actions: fix build over gatekeeper 2020-01-02 16:55:30 +01:00
86f16e2781 providers/oidc: fix incorrectly sorted imports 2020-01-02 16:42:52 +01:00
2ed8e72c62 new release: 0.7.11-beta 2020-01-02 16:38:11 +01:00
edeed18ae8 providers/oidc: fix error when using with app_gw 2020-01-02 16:38:01 +01:00
d24133d8a2 core: fix _redirect_with_qs appending an array to the URL 2020-01-02 16:14:56 +01:00
b9733e56aa providers/app_gw: fix passbook domain being empty 2020-01-02 16:09:17 +01:00
cd34413914 providers/app_gw: separate host field into external_ and internal_ 2020-01-02 16:09:04 +01:00
c3a4a76d43 providers/app_gw: fix Client's response_type not being set 2020-01-02 16:06:32 +01:00
a59a29b256 actions: also build gatekeeper on release 2020-01-02 15:55:39 +01:00
dce1edbe53 new release: 0.7.10-beta 2020-01-02 14:54:52 +01:00
264d43827a actions: create release based on version number, not tag name 2020-01-02 14:46:44 +01:00
6207226bdf new release: 0.7.9-beta 2020-01-02 14:09:58 +01:00
ebf33f39c9 actions: fix missing backslash for dockerbuild 2020-01-02 14:09:42 +01:00
696cd1f247 new release: 0.7.8-beta 2020-01-02 14:03:36 +01:00
b7b3abc462 actions: automatically create release when version/* tag is created, run tests before creating release 2020-01-02 13:49:24 +01:00
575739d07c ci: add bandit for static security checks 2020-01-02 13:41:49 +01:00
2d7e70eebf audit: fix import order 2020-01-02 13:20:41 +01:00
387f3c981f audit: fix error when trying to save models with UUID as PK 2020-01-02 13:12:23 +01:00
865435fb25 actions: fix path to helm chart 2020-01-02 11:38:54 +01:00
b10c5306b9 actions: ensure release gets only executed on release creation 2020-01-02 11:37:46 +01:00
7c706369cd new release: 0.7.7-beta 2020-01-02 11:22:08 +01:00
20dd6355c1 actions: run unittests in final docker images after build 2020-01-02 11:20:32 +01:00
ba8d5d6e27 actions: push both versioned and :latest tags 2020-01-02 11:19:55 +01:00
20 changed files with 236 additions and 64 deletions

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.7.6-beta current_version = 0.7.12-beta
tag = True tag = True
commit = True commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)

View File

@ -9,7 +9,7 @@ env:
jobs: jobs:
# Linting # Linting
pylint: pylint:
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
@ -26,7 +26,7 @@ jobs:
- name: Lint with pylint - name: Lint with pylint
run: pipenv run pylint passbook run: pipenv run pylint passbook
black: black:
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
@ -43,7 +43,7 @@ jobs:
- name: Lint with black - name: Lint with black
run: pipenv run black --check passbook run: pipenv run black --check passbook
prospector: prospector:
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
@ -59,6 +59,23 @@ jobs:
run: pip install -U pip pipenv && pipenv install --dev run: pip install -U pip pipenv && pipenv install --dev
- name: Lint with prospector - name: Lint with prospector
run: pipenv run prospector run: pipenv run prospector
bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-python@v1
with:
python-version: '3.7'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev
- name: Lint with bandit
run: pipenv run bandit -r passbook
# Actual CI tests # Actual CI tests
migrations: migrations:
needs: needs:
@ -78,7 +95,7 @@ jobs:
image: redis:latest image: redis:latest
ports: ports:
- 6379:6379 - 6379:6379
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1
@ -112,7 +129,7 @@ jobs:
image: redis:latest image: redis:latest
ports: ports:
- 6379:6379 - 6379:6379
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions/setup-python@v1 - uses: actions/setup-python@v1

View File

@ -1,13 +1,11 @@
name: passbook-release name: passbook-release
on: on:
release: release
types: # This configuration does not affect the page_build event above
- created
jobs: jobs:
# Build # Build
build-server: build-server:
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Docker Login Registry - name: Docker Login Registry
@ -16,11 +14,38 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image - name: Building Docker Image
run: docker build --no-cache -t beryju/passbook:0.7.6-beta -f Dockerfile . run: docker build
- name: Push Docker Container to Registry --no-cache
run: docker push beryju/passbook:0.7.6-beta -t beryju/passbook:0.7.12-beta
-t beryju/passbook:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.7.12-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest
build-gatekeeper:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: |
cd gatekeeper
docker build \
--no-cache \
-t beryju/passbook-gatekeeper:0.7.12-beta \
-t beryju/passbook-gatekeeper:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-gatekeeper:0.7.12-beta
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-gatekeeper:latest
build-static: build-static:
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
services: services:
postgres: postgres:
image: postgres:latest image: postgres:latest
@ -41,24 +66,24 @@ jobs:
run: docker build run: docker build
--no-cache --no-cache
--network=$(docker network ls | grep github | awk '{print $1}') --network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.7.6-beta -t beryju/passbook-static:0.7.12-beta
-t beryju/passbook-static:latest
-f static.Dockerfile . -f static.Dockerfile .
- name: Push Docker Container to Registry - name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.7.6-beta run: docker push beryju/passbook-static:0.7.12-beta
package-helm: - name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest
test-release:
needs: needs:
- build-server - build-server
- build-static - build-static
runs-on: [ubuntu-latest] runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Install Helm - name: Run test suite in final docker images
run: | run: |
apt update && apt install -y curl export PASSBOOK_DOMAIN=localhost
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash docker-compose pull
helm init docker-compose up --no-start
- name: Helm package docker-compose start postgresql redis
run: | docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
helm dependency update helm/passbook
helm package helm/passbook

60
.github/workflows/tag.yml vendored Normal file
View File

@ -0,0 +1,60 @@
on:
push:
tags:
- 'version/*'
name: passbook-version-tag
jobs:
build:
name: Create Release from Tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Pre-release test
run: |
export PASSBOOK_DOMAIN=localhost
docker-compose pull
docker build \
--no-cache \
-t beryju/passbook:latest \
-f Dockerfile .
docker-compose up --no-start
docker-compose start postgresql redis
docker-compose run -u root server bash -c "pip install --no-cache -r requirements-dev.txt && ./manage.py test"
- name: Install Helm
run: |
apt update && apt install -y curl
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
- name: Helm package
run: |
helm dependency update helm/
helm package helm/
mv passbook-*.tgz passbook-chart.tgz
- name: Extract verison number
id: get_version
uses: actions/github-script@0.2.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
return context.payload.ref.replace(/\/refs\/tags\/version\//, '');
- name: Create Release
id: create_release
uses: actions/create-release@v1.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.get_version.outputs.result }}
draft: false
prerelease: false
- name: Upload packaged Helm Chart
id: upload-release-asset
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./passbook-chart.tgz
asset_name: passbook-chart.tgz
asset_content_type: application/gzip

View File

@ -1,5 +1,7 @@
# passbook # passbook
![](https://github.com/BeryJu/passbook/workflows/passbook-ci/badge.svg)
## Quick instance ## Quick instance
``` ```

View File

@ -1,6 +1,6 @@
apiVersion: v1 apiVersion: v1
appVersion: "0.7.6-beta" appVersion: "0.7.12-beta"
description: A Helm chart for passbook. description: A Helm chart for passbook.
name: passbook name: passbook
version: "0.7.6-beta" version: "0.7.12-beta"
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png

View File

@ -2,7 +2,7 @@
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
image: image:
tag: 0.7.6-beta tag: 0.7.12-beta
nameOverride: "" nameOverride: ""

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = "0.7.6-beta" __version__ = "0.7.12-beta"

View File

@ -1,5 +1,6 @@
"""passbook audit models""" """passbook audit models"""
from enum import Enum from enum import Enum
from uuid import UUID
from inspect import getmodule, stack from inspect import getmodule, stack
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
@ -32,11 +33,15 @@ def sanitize_dict(source: Dict[Any, Any]) -> Dict[Any, Any]:
source[key] = sanitize_dict(value) source[key] = sanitize_dict(value)
elif isinstance(value, models.Model): elif isinstance(value, models.Model):
model_content_type = ContentType.objects.get_for_model(value) model_content_type = ContentType.objects.get_for_model(value)
source[key] = { source[key] = sanitize_dict(
{
"app": model_content_type.app_label, "app": model_content_type.app_label,
"name": model_content_type.model, "name": model_content_type.model,
"pk": value.pk, "pk": value.pk,
} }
)
elif isinstance(value, UUID):
source[key] = value.hex
return source return source

View File

@ -1,9 +1,11 @@
"""audit event tests""" """audit event tests"""
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase from django.test import TestCase
from guardian.shortcuts import get_anonymous_user from guardian.shortcuts import get_anonymous_user
from passbook.audit.models import Event, EventAction from passbook.audit.models import Event, EventAction
from passbook.core.models import Policy
class TestAuditEvent(TestCase): class TestAuditEvent(TestCase):
@ -11,6 +13,21 @@ class TestAuditEvent(TestCase):
def test_new_with_model(self): def test_new_with_model(self):
"""Create a new Event passing a model as kwarg""" """Create a new Event passing a model as kwarg"""
event = Event.new(EventAction.CUSTOM, model=get_anonymous_user()) event = Event.new(EventAction.CUSTOM, test={"model": get_anonymous_user()})
event.save() event.save() # We save to ensure nothing is un-saveable
self.assertIsNotNone(event.pk) model_content_type = ContentType.objects.get_for_model(get_anonymous_user())
self.assertEqual(
event.context.get("test").get("model").get("app"),
model_content_type.app_label,
)
def test_new_with_uuid_model(self):
"""Create a new Event passing a model (with UUID PK) as kwarg"""
temp_model = Policy.objects.create()
event = Event.new(EventAction.CUSTOM, model=temp_model)
event.save() # We save to ensure nothing is un-saveable
model_content_type = ContentType.objects.get_for_model(temp_model)
self.assertEqual(
event.context.get("model").get("app"), model_content_type.app_label
)
self.assertEqual(event.context.get("model").get("pk"), temp_model.pk.hex)

View File

@ -23,7 +23,7 @@ def _redirect_with_qs(view, get_query_set=None):
"""Wrapper to redirect whilst keeping GET Parameters""" """Wrapper to redirect whilst keeping GET Parameters"""
target = reverse(view) target = reverse(view)
if get_query_set: if get_query_set:
target += "?" + urlencode(get_query_set) target += "?" + urlencode(get_query_set.items())
return redirect(target) return redirect(target)

View File

@ -100,8 +100,8 @@ def gravatar(email, size=None, rating=None):
# gravatar uses md5 for their URLs, so md5 can't be avoided # gravatar uses md5 for their URLs, so md5 can't be avoided
gravatar_url = "%savatar/%s" % ( gravatar_url = "%savatar/%s" % (
"https://secure.gravatar.com/", "https://secure.gravatar.com/",
md5(email.encode("utf-8")).hexdigest(), md5(email.encode("utf-8")).hexdigest(), # nosec
) # nosec )
parameters = [p for p in (("s", size or "158"), ("r", rating or "g"),) if p[1]] parameters = [p for p in (("s", size or "158"), ("r", rating or "g"),) if p[1]]

View File

@ -20,6 +20,5 @@ class CreateAssignPermView(CreateView):
self.object._meta.app_label, self.object._meta.app_label,
self.object._meta.model_name, self.object._meta.model_name,
) )
print(full_permission)
assign_perm(full_permission, self.request.user, self.object) assign_perm(full_permission, self.request.user, self.object)
return response return response

View File

@ -1,7 +1,7 @@
"""passbook Application Security Gateway Forms""" """passbook Application Security Gateway Forms"""
from django import forms from django import forms
from oauth2_provider.generators import generate_client_id, generate_client_secret from oauth2_provider.generators import generate_client_id, generate_client_secret
from oidc_provider.models import Client from oidc_provider.models import Client, ResponseType
from passbook.providers.app_gw.models import ApplicationGatewayProvider from passbook.providers.app_gw.models import ApplicationGatewayProvider
@ -16,9 +16,14 @@ class ApplicationGatewayProviderForm(forms.ModelForm):
client_id=generate_client_id(), client_secret=generate_client_secret() client_id=generate_client_id(), client_secret=generate_client_secret()
) )
self.instance.client.name = self.instance.name 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 = [ self.instance.client.redirect_uris = [
f"http://{self.instance.host}/oauth2/callback", f"http://{self.instance.external_host}/oauth2/callback",
f"https://{self.instance.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.scope = ["openid", "email"]
self.instance.client.save() self.instance.client.save()
@ -27,8 +32,9 @@ class ApplicationGatewayProviderForm(forms.ModelForm):
class Meta: class Meta:
model = ApplicationGatewayProvider model = ApplicationGatewayProvider
fields = ["name", "host"] fields = ["name", "internal_host", "external_host"]
widgets = { widgets = {
"name": forms.TextInput(), "name": forms.TextInput(),
"host": forms.TextInput(), "internal_host": forms.TextInput(),
"external_host": forms.TextInput(),
} }

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

@ -14,7 +14,8 @@ class ApplicationGatewayProvider(Provider):
"""This provider uses oauth2_proxy with the OIDC Provider.""" """This provider uses oauth2_proxy with the OIDC Provider."""
name = models.TextField() name = models.TextField()
host = models.TextField() internal_host = models.TextField()
external_host = models.TextField()
client = models.ForeignKey(Client, on_delete=models.CASCADE) client = models.ForeignKey(Client, on_delete=models.CASCADE)

View File

@ -40,10 +40,10 @@ services:
environment: environment:
OAUTH2_PROXY_CLIENT_ID: {{ provider.client.client_id }} OAUTH2_PROXY_CLIENT_ID: {{ provider.client.client_id }}
OAUTH2_PROXY_CLIENT_SECRET: {{ provider.client.client_secret }} OAUTH2_PROXY_CLIENT_SECRET: {{ provider.client.client_secret }}
OAUTH2_PROXY_REDIRECT_URL: https://{{ provider.host }}/oauth2/callback OAUTH2_PROXY_REDIRECT_URL: https://{{ provider.external_host }}/oauth2/callback
OAUTH2_PROXY_OIDC_ISSUER_URL: https://{{ request.META.host }}/application/oidc OAUTH2_PROXY_OIDC_ISSUER_URL: https://{{ request.META.HTTP_HOST }}/application/oidc
OAUTH2_PROXY_COOKIE_SECRET: {{ cookie_secret }} OAUTH2_PROXY_COOKIE_SECRET: {{ cookie_secret }}
OAUTH2_PROXY_UPSTREAM: http://{{ provider.host }}</textarea> OAUTH2_PROXY_UPSTREAM: http://{{ provider.internal_host }}</textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">{% trans 'Close' %}</button> <button type="button" class="btn btn-primary" data-dismiss="modal">{% trans 'Close' %}</button>

View File

@ -1,21 +1,38 @@
"""OIDC Permission checking""" """OIDC Permission checking"""
from typing import Optional
from django.contrib import messages from django.contrib import messages
from django.db.models.deletion import Collector
from django.http import HttpRequest, HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect
from oidc_provider.models import Client
from structlog import get_logger from structlog import get_logger
from passbook.audit.models import Event, EventAction from passbook.audit.models import Event, EventAction
from passbook.core.models import Application from passbook.core.models import Application, Provider, User
from passbook.policies.engine import PolicyEngine from passbook.policies.engine import PolicyEngine
LOGGER = get_logger() LOGGER = get_logger()
def check_permissions(request, user, client): def check_permissions(
request: HttpRequest, user: User, client: Client
) -> Optional[HttpResponse]:
"""Check permissions, used for """Check permissions, used for
https://django-oidc-provider.readthedocs.io/en/latest/ https://django-oidc-provider.readthedocs.io/en/latest/
sections/settings.html#oidc-after-userlogin-hook""" sections/settings.html#oidc-after-userlogin-hook"""
try: try:
application = client.openidprovider.application # because oidc_provider is also used by app_gw, we can't be
# sure an OpenIDPRovider instance exists. hence we look through all related models
# and choose the one that inherits from Provider, which is guaranteed to
# have the application property
collector = Collector(using="default")
collector.collect([client])
for _, related in collector.data.items():
related_object = next(iter(related))
if isinstance(related_object, Provider):
application = related_object.application
break
except Application.DoesNotExist: except Application.DoesNotExist:
return redirect("passbook_providers_oauth:oauth2-permission-denied") return redirect("passbook_providers_oauth:oauth2-permission-denied")
LOGGER.debug( LOGGER.debug(

View File

@ -13,11 +13,11 @@ class MetricsView(View):
def get(self, request: HttpRequest) -> HttpResponse: def get(self, request: HttpRequest) -> HttpResponse:
"""Check for HTTP-Basic auth""" """Check for HTTP-Basic auth"""
auth_header = request.META.get("HTTP_AUTHORIZATION", "") auth_header = request.META.get("HTTP_AUTHORIZATION", "")
token_type, _, credentials = auth_header.partition(" ") auth_type, _, credentials = auth_header.partition(" ")
creds = f"monitor:{settings.SECRET_KEY}" credentials = f"monitor:{settings.SECRET_KEY}"
expected = b64encode(str.encode(creds)).decode() expected = b64encode(str.encode(credentials)).decode()
if token_type != "Basic" or credentials != expected: if auth_type != "Basic" or credentials != expected:
raise Http404 raise Http404
return ExportToDjangoView(request) return ExportToDjangoView(request)

View File

@ -34,8 +34,7 @@ ENV PASSBOOK_POSTGRESQL__USER=passbook
ENV PASSBOOK_POSTGRESQL__PASSWORD="EK-5jnKfjrGRm<77" ENV PASSBOOK_POSTGRESQL__PASSWORD="EK-5jnKfjrGRm<77"
RUN ./manage.py collectstatic --no-input RUN ./manage.py collectstatic --no-input
FROM docker.beryju.org/pixie/server FROM beryju/pixie:latest
COPY --from=static-build /app/static /data/static/ COPY --from=static-build /app/static /web-root/static/
COPY --from=static-build /app/static/robots.txt /data/robots.txt COPY --from=static-build /app/static/robots.txt /web-root/robots.txt
WORKDIR /data