hswaw/paperless: initial deployment

Change-Id: Ie6fb0df0bfa047e4fd561c6de8b26ab0fbebbcb8
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1305
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/hswaw/paperless/Dockerfile b/hswaw/paperless/Dockerfile
new file mode 100644
index 0000000..af9a3ee
--- /dev/null
+++ b/hswaw/paperless/Dockerfile
@@ -0,0 +1,15 @@
+# Note: this is required to run the container as non-root.
+
+# Update:
+# export VERSION=1.7.0
+# docker build --build-arg VERSION -t registry.k0.hswaw.net/informatic/paperless-ngx:$VERSION .
+# docker push registry.k0.hswaw.net/informatic/paperless-ngx:$VERSION
+
+ARG VERSION=1.7.0
+FROM ghcr.io/paperless-ngx/paperless-ngx:${VERSION}
+
+# Install polish tesseract training data
+RUN apt-get update && apt-get install -y tesseract-ocr-pol && rm -rf /var/lib/apt/lists/*
+
+# Remove privilege dropping and use paperless user directly everywhere
+RUN sed -i 's/gosu paperless//g' /sbin/docker-entrypoint.sh && sed -i -e 's;user=.*;;g' -e 's;logfile=/var/.*;logfile=/dev/null;g' /etc/supervisord.conf
diff --git a/hswaw/paperless/paperless.libsonnet b/hswaw/paperless/paperless.libsonnet
new file mode 100644
index 0000000..bed51e9
--- /dev/null
+++ b/hswaw/paperless/paperless.libsonnet
@@ -0,0 +1,188 @@
+# kubectl -n paperless create secret generic paperless-proxy --from-literal=cookie_secret=$(pwgen 32 1) --from-literal=oidc_secret=...
+# kubectl -n paperless create secret generic paperless --from-literal=postgres_password=$(pwgen 32 1) --from-literal=redis_password=$(pwgen 32 1) --from-literal=secret_key=$(pwgen 32 1)
+
+# There is no way of handling superusers (Admin panel access) automatically when
+# using OAuth2-Proxy, thus we need to run the following command to mark the
+# first user as such:
+#   kubectl -n paperless exec -it deploy/paperless -c paperless -- python ./manage.py shell -c "from django.contrib.auth.models import User; u = User.objects.get_by_natural_key('informatic'); u.is_superuser = True; u.is_staff = True; u.save()"
+
+local kube = import "../../kube/kube.libsonnet";
+local postgres = import "../../kube/postgres.libsonnet";
+local redis = import "../../kube/redis.libsonnet";
+
+{
+    local app = self,
+    local cfg = self.cfg,
+
+    cfg:: {
+        namespace: "paperless",
+        domain: "paperless.hackerspace.pl",
+
+        images: {
+            paperless: "registry.k0.hswaw.net/informatic/paperless-ngx@sha256:78b17e3050f7edea1e8c659c433ebcb6365bb93280a2698e3322075f988b1f41",
+            proxy: "quay.io/oauth2-proxy/oauth2-proxy:v7.2.1",
+        },
+
+        storageClassName: "waw-hdd-redundant-3",
+    },
+
+    ns: kube.Namespace(cfg.namespace),
+
+    redis: redis {
+        cfg+: {
+            namespace: cfg.namespace,
+            storageClassName: cfg.storageClassName,
+            appName: "paperless",
+            image: "redis:6.0",
+            password: { secretKeyRef: { name: "paperless", key: "redis_password" } },
+        },
+    },
+
+    postgres: postgres {
+        cfg+: {
+            namespace: cfg.namespace,
+            appName: "paperless",
+            database: "paperless",
+            username: "paperless",
+
+            password: { secretKeyRef: { name: "paperless", key: "postgres_password" } },
+            storageClassName: cfg.storageClassName,
+            storageSize: "20Gi",
+        },
+        bouncer: {},
+    },
+
+    dataVolume: app.ns.Contain(kube.PersistentVolumeClaim("paperless-data")) {
+        spec+: {
+            storageClassName: cfg.storageClassName,
+            accessModes: [ "ReadWriteOnce" ],
+            resources: {
+                requests: {
+                    storage: "100Gi",
+                },
+            },
+        },
+    },
+
+    deploy: app.ns.Contain(kube.Deployment("paperless")) {
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        data: kube.PersistentVolumeClaimVolume(app.dataVolume),
+                    },
+
+                    securityContext: {
+                        runAsUser: 1000,
+                        runAsGroup: 1000,
+                        fsGroup: 1000,
+                    },
+
+                    default_container:: "auth",
+                    containers_: {
+                        auth: kube.Container("authproxy") {
+                            image: cfg.images.proxy,
+                            ports_: {
+                                http: { containerPort: 8001 },
+                            },
+
+                            env_: {
+                                OAUTH2_PROXY_UPSTREAMS: "http://127.0.0.1:8000",
+                                OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:8001",
+
+                                OAUTH2_PROXY_COOKIE_SECRET: { secretKeyRef: { name: "paperless-proxy", key: "cookie_secret" } },
+
+                                OAUTH2_PROXY_PROVIDER: "oidc",
+                                OAUTH2_PROXY_OIDC_ISSUER_URL: "https://sso.hackerspace.pl",
+                                OAUTH2_PROXY_SKIP_PROVIDER_BUTTON: "true",
+
+                                OAUTH2_PROXY_CLIENT_ID: "b4859334-140b-432a-81f6-8f3e135e021a",
+                                OAUTH2_PROXY_CLIENT_SECRET: { secretKeyRef: { name: "paperless-proxy", key: "oidc_secret" } },
+
+                                OAUTH2_PROXY_EMAIL_DOMAINS: "*",
+                                OAUTH2_PROXY_ALLOWED_GROUPS: "zarzad",
+
+                                # Security considerations:
+                                #
+                                # * OAuth2-Proxy *will* strip X-Forwarded-User
+                                #   header from requests passed through to
+                                #   endpoint, preventing authentication bypass
+                                #
+                                # * OAuth2-Proxy *will not* strip Authorization
+                                #   header - that can either be a user token,
+                                #   or a username/password pair. Former can only
+                                #   be generated by staff/superuser in Admin
+                                #   panel, and the latter will not work for our
+                                #   OAuth2 autogenerated users since these do
+                                #   not have any password set
+                                OAUTH2_PROXY_SKIP_AUTH_ROUTES: "^/api/.*",
+                            },
+                        },
+
+                        paperless: kube.Container("paperless") {
+                            image: cfg.images.paperless,
+                            resources: {
+                                requests: { cpu: "500m", memory: "1024M" },
+                                limits: { cpu: "4", memory: "6144M" },
+                            },
+                            env_: {
+                                PAPERLESS_PORT: "8000",
+
+                                PAPERLESS_SECRET_KEY: { secretKeyRef: { name: "paperless", key: "secret_key" } },
+
+                                A_REDIS_PASSWORD: app.redis.cfg.password,
+                                PAPERLESS_REDIS: "redis://:$(A_REDIS_PASSWORD)@redis:6379",
+
+                                PAPERLESS_DBHOST: "postgres",
+                                PAPERLESS_DBNAME: app.postgres.cfg.database,
+                                PAPERLESS_DBUSER: app.postgres.cfg.username,
+                                PAPERLESS_DBPASS: app.postgres.cfg.password,
+
+                                PAPERLESS_ENABLE_HTTP_REMOTE_USER: "true",
+                                PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME: "HTTP_X_FORWARDED_USER",
+
+                                PAPERLESS_OCR_LANGUAGE: "pol",
+                                PAPERLESS_OCR_MODE: "force",
+                                PAPERLESS_DATE_ORDER: "YMD",
+                            },
+
+                            volumeMounts: [
+                                { name: "data", mountPath: "/usr/src/paperless/data", subPath: "data" },
+                                { name: "data", mountPath: "/usr/src/paperless/media", subPath: "media" },
+                                { name: "data", mountPath: "/usr/src/paperless/consume", subPath: "consume" },
+                            ],
+                        },
+                    },
+                },
+            },
+        },
+    },
+
+    service: app.ns.Contain(kube.Service("paperless")) {
+        target_pod:: app.deploy.spec.template,
+    },
+
+    ingress: app.ns.Contain(kube.Ingress("paperless")) {
+        metadata+: {
+            annotations+: {
+                "kubernetes.io/tls-acme": "true",
+                "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+                "nginx.ingress.kubernetes.io/proxy-body-size": "0",
+            },
+        },
+        spec+: {
+            tls: [ { hosts: [ cfg.domain ], secretName: "paperless-tls" } ],
+            rules: [
+                {
+                    host: cfg.domain,
+                    http: {
+                        paths:  [
+                            { path: "/", backend: app.service.name_port },
+                        ],
+                    },
+                },
+            ],
+        },
+    }
+}
diff --git a/hswaw/paperless/prod.jsonnet b/hswaw/paperless/prod.jsonnet
new file mode 100644
index 0000000..8450836
--- /dev/null
+++ b/hswaw/paperless/prod.jsonnet
@@ -0,0 +1,5 @@
+local paperless = import "./paperless.libsonnet";
+
+{
+    prod: paperless {},
+}