Compare commits

..

9 Commits

Author SHA1 Message Date
a4cc653757 new release: 0.12.3-stable 2020-10-20 10:24:45 +02:00
db4ff20906 outposts: fix service using incorrect pod selector 2020-10-20 10:18:05 +02:00
1f0fbd33b6 build(deps): bump urllib3 from 1.25.10 to 1.25.11 (#287)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.10 to 1.25.11.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.25.10...1.25.11)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-20 10:17:46 +02:00
5de8d2721e build(deps): bump uvicorn from 0.12.1 to 0.12.2 (#286)
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.12.1 to 0.12.2.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.12.1...0.12.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-20 10:09:37 +02:00
0d65da9a9e build(deps): bump boto3 from 1.15.18 to 1.16.0 (#288)
Bumps [boto3](https://github.com/boto/boto3) from 1.15.18 to 1.16.0.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.15.18...1.16.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-10-20 09:34:55 +02:00
4316ee4330 root: implement db backups with monitored task, update docs 2020-10-19 22:17:47 +02:00
2ed9a1dbe3 */tasks: update phrasing 2020-10-19 21:35:31 +02:00
8e03824d20 lib: always set task's UID, even for unexpected errors 2020-10-19 21:30:21 +02:00
754dbdd0e5 outpost: fix logs for kubernetes controller 2020-10-19 21:29:58 +02:00
22 changed files with 124 additions and 116 deletions

View File

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

View File

@ -18,11 +18,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/passbook:0.12.2-stable
-t beryju/passbook:0.12.3-stable
-t beryju/passbook:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.12.2-stable
run: docker push beryju/passbook:0.12.3-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest
build-proxy:
@ -48,11 +48,11 @@ jobs:
cd proxy
docker build \
--no-cache \
-t beryju/passbook-proxy:0.12.2-stable \
-t beryju/passbook-proxy:0.12.3-stable \
-t beryju/passbook-proxy:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-proxy:0.12.2-stable
run: docker push beryju/passbook-proxy:0.12.3-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-proxy:latest
build-static:
@ -77,11 +77,11 @@ jobs:
run: docker build
--no-cache
--network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.12.2-stable
-t beryju/passbook-static:0.12.3-stable
-t beryju/passbook-static:latest
-f static.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.12.2-stable
run: docker push beryju/passbook-static:0.12.3-stable
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest
test-release:
@ -114,5 +114,5 @@ jobs:
SENTRY_PROJECT: passbook
SENTRY_URL: https://sentry.beryju.org
with:
tagName: 0.12.2-stable
tagName: 0.12.3-stable
environment: beryjuorg-prod

36
Pipfile.lock generated
View File

@ -74,18 +74,18 @@
},
"boto3": {
"hashes": [
"sha256:9ab957090f7893172768bb8b8d2c5cce0afd36a9d36d73a9fb14168f72d75a8b",
"sha256:f56148e2c6b9a2d704218da42f07d72f00270bfddb13bc1bdea20d3327daa51e"
"sha256:2e16f02c8b832d401d958d7ca0a14c5bc7da17827918e6b24e5bc43dce8f496e",
"sha256:ab5353a968a4e664b9da2dd950169b755066525fcbfdfc90e7e49c8333d95c19"
],
"index": "pypi",
"version": "==1.15.18"
"version": "==1.16.0"
},
"botocore": {
"hashes": [
"sha256:de5f9fc0c7e88ee7ba831fa27475be258ae09ece99143ed623d3618a3c84ee2c",
"sha256:e224754230e7e015836ba20037cac6321e8e2ce9b8627c14d579fcb37249decd"
"sha256:226effa72e3ddd0a802e812c0e204999393ca7982fee754cc0c770a7a1caef3a",
"sha256:9bf8586b69f20cf0a8ed1e27338cd10ce847751d1a2fd98b92662565c8a2df24"
],
"version": "==1.18.18"
"version": "==1.19.0"
},
"cachetools": {
"hashes": [
@ -1100,23 +1100,23 @@
"secure"
],
"hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
],
"index": "pypi",
"markers": null,
"version": "==1.25.10"
"version": "==1.25.11"
},
"uvicorn": {
"extras": [
"standard"
],
"hashes": [
"sha256:a461e76406088f448f36323f5ac774d50e5a552b6ccb54e4fca8d83ef614a7c2",
"sha256:d06a25caa8dc680ad92eb3ec67363f5281c092059613a1cc0100acba37fc0f45"
"sha256:8ff7495c74b8286a341526ff9efa3988ebab9a4b2f561c7438c3cb420992d7dd",
"sha256:e5dbed4a8a44c7b04376021021d63798d6a7bcfae9c654a0b153577b93854fba"
],
"index": "pypi",
"version": "==0.12.1"
"version": "==0.12.2"
},
"uvloop": {
"hashes": [
@ -1476,10 +1476,10 @@
},
"pbr": {
"hashes": [
"sha256:14bfd98f51c78a3dd22a1ef45cf194ad79eee4a19e8e1a0d5c7f8e81ffe182ea",
"sha256:5adc0f9fc64319d8df5ca1e4e06eea674c26b80e6f00c530b18ce6a6592ead15"
"sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9",
"sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"
],
"version": "==5.5.0"
"version": "==5.5.1"
},
"pep8-naming": {
"hashes": [
@ -1745,12 +1745,12 @@
"secure"
],
"hashes": [
"sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
"sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
],
"index": "pypi",
"markers": null,
"version": "==1.25.10"
"version": "==1.25.11"
},
"wrapt": {
"hashes": [

View File

@ -19,7 +19,7 @@ services:
networks:
- internal
server:
image: beryju/passbook:${PASSBOOK_TAG:-0.12.2-stable}
image: beryju/passbook:${PASSBOOK_TAG:-0.12.3-stable}
command: server
environment:
PASSBOOK_REDIS__HOST: redis
@ -40,7 +40,7 @@ services:
env_file:
- .env
worker:
image: beryju/passbook:${PASSBOOK_TAG:-0.12.2-stable}
image: beryju/passbook:${PASSBOOK_TAG:-0.12.3-stable}
command: worker
networks:
- internal
@ -54,7 +54,7 @@ services:
env_file:
- .env
static:
image: beryju/passbook-static:${PASSBOOK_TAG:-0.12.2-stable}
image: beryju/passbook-static:${PASSBOOK_TAG:-0.12.3-stable}
networks:
- internal
labels:

View File

@ -13,7 +13,7 @@ Download the latest `docker-compose.yml` from [here](https://raw.githubuserconte
To optionally enable error-reporting, run `echo PASSBOOK_ERROR_REPORTING__ENABLED=true >> .env`
To optionally deploy a different version run `echo PASSBOOK_TAG=0.12.2-stable >> .env`
To optionally deploy a different version run `echo PASSBOOK_TAG=0.12.3-stable >> .env`
If this is a fresh passbook install run the following commands to generate a password:

View File

@ -11,7 +11,7 @@ This installation automatically applies database migrations on startup. After th
image:
name: beryju/passbook
name_static: beryju/passbook-static
tag: 0.12.2-stable
tag: 0.12.3-stable
nameOverride: ""

View File

@ -6,6 +6,10 @@
### Backup
!!! notice
Local backups are **enabled** by default, and will be run daily at 00:00
Local backups can be created by running the following command in your passbook installation directory
```
@ -14,15 +18,6 @@ docker-compose run --rm worker backup
This will dump the current database into the `./backups` folder. By defaults, the last 10 Backups are kept.
To schedule these backups, use the following snippet in a crontab
```
0 0 * * * bash -c "cd <passbook install location> && docker-compose run --rm worker backup" >/dev/null
```
!!! notice
passbook does support automatic backups on a schedule, however this is currently not recommended, as there is no way to monitor these scheduled tasks.
### Restore
@ -42,11 +37,7 @@ After you've restored the backup, it is recommended to restart all services with
### S3 Configuration
!!! notice
To trigger backups with S3 enabled, use the same commands as above.
#### S3 Preparation
#### Preparation
passbook expects the bucket you select to already exist. The IAM User given to passbook should have the following permissions
@ -101,11 +92,11 @@ Simply enable these options in your values.yaml file
```yaml
# Enable Database Backups to S3
backup:
access_key: access-key
secret_key: secret-key
accessKey: access-key
secretKey: secret-key
bucket: s3-bucket
region: eu-central-1
host: s3-host
```
Afterwards, run a `helm upgrade` to update the ConfigMap. Because passbook-scheduled backups are not recommended currently, a Kubernetes CronJob is created that runs the backup daily.
Afterwards, run a `helm upgrade` to update the ConfigMap. Backups are done automatically as above, at 00:00 every day.

View File

@ -1,8 +1,8 @@
apiVersion: v2
appVersion: "0.12.2-stable"
appVersion: "0.12.3-stable"
description: A Helm chart for passbook.
name: passbook
version: "0.12.2-stable"
version: "0.12.3-stable"
icon: https://github.com/BeryJu/passbook/blob/master/docs/images/logo.svg
dependencies:
- name: postgresql

View File

@ -1,42 +0,0 @@
{{- if .Values.backup }}
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: {{ include "passbook.fullname" . }}-backup
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
schedule: "0 0 * * *"
jobTemplate:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.name }}:{{ .Values.image.tag }}"
args: [server]
envFrom:
- configMapRef:
name: {{ include "passbook.fullname" . }}-config
prefix: PASSBOOK_
env:
- name: PASSBOOK_SECRET_KEY
valueFrom:
secretKeyRef:
name: "{{ include "passbook.fullname" . }}-secret-key"
key: "secret_key"
- name: PASSBOOK_REDIS__PASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-redis"
key: "redis-password"
- name: PASSBOOK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-postgresql"
key: "postgresql-password"
{{- end}}

View File

@ -4,7 +4,7 @@
image:
name: beryju/passbook
name_static: beryju/passbook-static
tag: 0.12.2-stable
tag: 0.12.3-stable
nameOverride: ""

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = "0.12.2-stable"
__version__ = "0.12.3-stable"

View File

@ -79,11 +79,18 @@ class MonitoredTask(Task):
_result: TaskResult
_uid: Optional[str]
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.save_on_success = True
self._uid = None
self._result = TaskResult(status=TaskResultStatus.ERROR, messages=[])
def set_uid(self, uid: str):
"""Set UID, so in the case of an unexpected error its saved correctly"""
self._uid = uid
def set_status(self, result: TaskResult):
"""Set result for current run, will overwrite previous result."""
self._result = result
@ -92,6 +99,8 @@ class MonitoredTask(Task):
def after_return(
self, status, retval, task_id, args: List[Any], kwargs: Dict[str, Any], einfo
):
if not self._result.uid:
self._result.uid = self._uid
if self.save_on_success:
TaskInfo(
task_name=self.__name__,
@ -107,6 +116,8 @@ class MonitoredTask(Task):
# pylint: disable=too-many-arguments
def on_failure(self, exc, task_id, args, kwargs, einfo):
if not self._result.uid:
self._result.uid = self._uid
TaskInfo(
task_name=self.__name__,
task_description=self.__doc__,

View File

@ -0,0 +1,34 @@
"""Database backup task"""
from datetime import datetime
from io import StringIO
from botocore.exceptions import BotoCoreError, ClientError
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.core import management
from structlog import get_logger
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
from passbook.root.celery import CELERY_APP
LOGGER = get_logger()
@CELERY_APP.task(bind=True, base=MonitoredTask)
def backup_database(self: MonitoredTask): # pragma: no cover
"""Database backup"""
try:
start = datetime.now()
out = StringIO()
management.call_command("dbbackup", quiet=True, stdout=out)
self.set_status(
TaskResult(
TaskResultStatus.SUCCESSFUL,
[
f"Successfully finished database backup {naturaltime(start)}",
out.getvalue(),
],
)
)
LOGGER.info("Successfully backed up database.")
except (IOError, BotoCoreError, ClientError) as exc:
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))

View File

@ -33,7 +33,7 @@ class BaseController:
"""Call .up() but capture all log output and return it."""
with capture_logs() as logs:
self.up()
return [f"{x['controller']}: {x['event']}" for x in logs]
return [x["event"] for x in logs]
def down(self):
"""Handler to delete everything we've created"""

View File

@ -7,6 +7,7 @@ from passbook.outposts.controllers.k8s.base import (
KubernetesObjectReconciler,
NeedsUpdate,
)
from passbook.outposts.controllers.k8s.deployment import DeploymentReconciler
if TYPE_CHECKING:
from passbook.outposts.controllers.kubernetes import KubernetesController
@ -36,9 +37,10 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
ports = []
for port_name, port in self.controller.deployment_ports.items():
ports.append(V1ServicePort(name=port_name, port=port))
selector_labels = DeploymentReconciler(self.controller).get_pod_meta()
return V1Service(
metadata=meta,
spec=V1ServiceSpec(ports=ports, selector=meta.labels, type="ClusterIP"),
spec=V1ServiceSpec(ports=ports, selector=selector_labels, type="ClusterIP"),
)
def create(self, reference: V1Service):

View File

@ -5,6 +5,7 @@ from typing import Dict, List, Type
from kubernetes.client import OpenApiException
from kubernetes.config import load_incluster_config, load_kube_config
from kubernetes.config.config_exception import ConfigException
from structlog.testing import capture_logs
from yaml import dump_all
from passbook.outposts.controllers.base import BaseController, ControllerException
@ -43,6 +44,18 @@ class KubernetesController(BaseController):
except OpenApiException as exc:
raise ControllerException from exc
def up_with_logs(self) -> List[str]:
try:
all_logs = []
for reconcile_key in self.reconcile_order:
with capture_logs() as logs:
reconciler = self.reconcilers[reconcile_key](self)
reconciler.up()
all_logs += [f"{reconcile_key.title()}: {x['event']}" for x in logs]
return all_logs
except OpenApiException as exc:
raise ControllerException from exc
def down(self):
try:
for reconcile_key in self.reconcile_order:

View File

@ -35,9 +35,10 @@ def outpost_controller_all():
@CELERY_APP.task(bind=True, base=MonitoredTask)
def outpost_controller(self: MonitoredTask, outpost_pk: str):
"""Launch controller deployment of Outpost"""
"""Create/update/monitor the deployment of an Outpost"""
logs = []
outpost: Outpost = Outpost.objects.get(pk=outpost_pk)
self.set_uid(slugify(outpost.name))
try:
if outpost.type == OutpostType.PROXY:
if outpost.deployment_type == OutpostDeploymentType.KUBERNETES:
@ -45,15 +46,9 @@ def outpost_controller(self: MonitoredTask, outpost_pk: str):
if outpost.deployment_type == OutpostDeploymentType.DOCKER:
logs = ProxyDockerController(outpost).up_with_logs()
except ControllerException as exc:
self.set_status(
TaskResult(TaskResultStatus.ERROR, uid=slugify(outpost.name)).with_error(
exc
)
)
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
else:
self.set_status(
TaskResult(TaskResultStatus.SUCCESSFUL, logs, uid=slugify(outpost.name))
)
self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs))
@CELERY_APP.task()

View File

@ -269,9 +269,14 @@ CELERY_TASK_SOFT_TIME_LIMIT = 600
CELERY_BEAT_SCHEDULE = {
"clean_expired_models": {
"task": "passbook.core.tasks.clean_expired_models",
"schedule": crontab(minute="*/5"), # Run every 5 minutes
"schedule": crontab(minute="*/5"),
"options": {"queue": "passbook_scheduled"},
}
},
"db_backup": {
"task": "passbook.lib.tasks.backup.backup_database",
"schedule": crontab(minute=0, hour=0),
"options": {"queue": "passbook_scheduled"},
},
}
CELERY_TASK_CREATE_MISSING_QUEUES = True
CELERY_TASK_DEFAULT_QUEUE = "passbook"
@ -445,6 +450,7 @@ for _app in INSTALLED_APPS:
if DEBUG:
INSTALLED_APPS.append("debug_toolbar")
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
CELERY_TASK_ALWAYS_EAGER = True
INSTALLED_APPS.append("passbook.core.apps.PassbookCoreConfig")

View File

@ -20,8 +20,9 @@ def ldap_sync_all():
@CELERY_APP.task(bind=True, base=MonitoredTask)
def ldap_sync(self: MonitoredTask, source_pk: int):
"""Sync a single source"""
"""Synchronization of an LDAP Source"""
source: LDAPSource = LDAPSource.objects.get(pk=source_pk)
self.set_uid(slugify(source.name))
try:
syncer = LDAPSynchronizer(source)
user_count = syncer.sync_users()
@ -33,10 +34,7 @@ def ldap_sync(self: MonitoredTask, source_pk: int):
TaskResult(
TaskResultStatus.SUCCESSFUL,
[f"Synced {user_count} users", f"Synced {group_count} groups"],
uid=slugify(source.name),
)
)
except LDAPException as exc:
self.set_status(
TaskResult(TaskResultStatus.ERROR, uid=slugify(source.name)).with_error(exc)
)
self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))

View File

@ -13,7 +13,7 @@ LOGGER = get_logger()
@CELERY_APP.task(bind=True, base=MonitoredTask)
def clean_temporary_users(self: MonitoredTask):
"""Remove old temporary users"""
"""Remove temporary users created by SAML Sources"""
_now = now()
messages = []
deleted_users = 0

View File

@ -37,6 +37,8 @@ def send_mails(stage: EmailStage, *messages: List[EmailMultiAlternatives]):
def send_mail(self: MonitoredTask, email_stage_pk: int, message: Dict[Any, Any]):
"""Send Email for Email Stage. Retries are scheduled automatically."""
self.save_on_success = False
message_id = make_msgid(domain=DNS_NAME)
self.set_uid(message_id)
try:
stage: EmailStage = EmailStage.objects.get(pk=email_stage_pk)
backend = stage.backend
@ -48,7 +50,6 @@ def send_mail(self: MonitoredTask, email_stage_pk: int, message: Dict[Any, Any])
setattr(message_object, key, value)
message_object.from_email = stage.from_address
# Because we use the Message-ID as UID for the task, manually assign it
message_id = make_msgid(domain=DNS_NAME)
message_object.extra_headers["Message-ID"] = message_id
LOGGER.debug("Sending mail", to=message_object.to)
@ -57,7 +58,6 @@ def send_mail(self: MonitoredTask, email_stage_pk: int, message: Dict[Any, Any])
TaskResult(
TaskResultStatus.SUCCESSFUL,
messages=["Successfully sent Mail."],
uid=message_id,
)
)
except (SMTPException, ConnectionError) as exc:

View File

@ -1,3 +1,3 @@
package pkg
const VERSION = "0.12.2-stable"
const VERSION = "0.12.3-stable"