From bb390b4029e1393b8eef76dc568779823a8844e7 Mon Sep 17 00:00:00 2001
From: Vincent Pelletier <vincent@nexedi.com>
Date: Thu, 10 Aug 2017 13:36:55 +0900
Subject: [PATCH] stack/caucase,stack/erp5,software/caucase: Update for caucase
 0.9.5

Notes

stack/erp5

 * The service-auto-approve-amount to default is set to 1, in order that the
   only needed service is automatically approved.  As caucase is accessed
   internally (on local IPv4) only partitions on the same server will access
   caucase.
---
 software/caucase/buildout.hash.cfg            |  22 ++
 .../instance-caucase-input-schema.json        |  82 ++----
 .../instance-caucase-output-schema.json       |   8 +-
 software/caucase/instance-caucased.cfg.jinja2 |  32 +++
 software/caucase/instance.cfg.jinja2          |  34 +++
 software/caucase/software.cfg                 |  29 +-
 software/erp5/README.rst                      |   1 +
 software/erp5/instance-erp5-input-schema.json |  34 ++-
 stack/caucase/README.rst                      |  83 ++++++
 stack/caucase/buildout.cfg                    | 157 ++---------
 stack/caucase/buildout.hash.cfg               |  22 +-
 stack/caucase/ca-nginx.conf.in                |  95 -------
 stack/caucase/caucase.jinja2.library          | 104 ++++++++
 .../instance-auth-server.cfg.jinja2.in        | 152 -----------
 stack/caucase/instance-caucase.cfg.jinja2.in  | 248 ------------------
 stack/caucase/instance.cfg.in                 |  43 ---
 stack/caucase/template-httpd-auth.conf.in     |  84 ------
 stack/erp5/buildout.cfg                       |   9 +-
 stack/erp5/buildout.hash.cfg                  |   8 +-
 stack/erp5/instance-balancer.cfg.in           | 165 ++----------
 stack/erp5/instance-erp5.cfg.in               |  60 +++--
 stack/erp5/instance-zope.cfg.in               |  46 +---
 stack/erp5/instance.cfg.in                    |  11 +-
 23 files changed, 467 insertions(+), 1062 deletions(-)
 create mode 100644 software/caucase/buildout.hash.cfg
 create mode 100644 software/caucase/instance-caucased.cfg.jinja2
 create mode 100644 software/caucase/instance.cfg.jinja2
 create mode 100644 stack/caucase/README.rst
 delete mode 100644 stack/caucase/ca-nginx.conf.in
 create mode 100644 stack/caucase/caucase.jinja2.library
 delete mode 100644 stack/caucase/instance-auth-server.cfg.jinja2.in
 delete mode 100644 stack/caucase/instance-caucase.cfg.jinja2.in
 delete mode 100644 stack/caucase/instance.cfg.in
 delete mode 100644 stack/caucase/template-httpd-auth.conf.in

diff --git a/software/caucase/buildout.hash.cfg b/software/caucase/buildout.hash.cfg
new file mode 100644
index 000000000..f825b508a
--- /dev/null
+++ b/software/caucase/buildout.hash.cfg
@@ -0,0 +1,22 @@
+# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax.
+# The only allowed lines here are (regexes):
+# - "^#" comments, copied verbatim
+# - "^[" section beginings, copied verbatim
+# - lines containing an "=" sign which must fit in the following categorie.
+#   - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file
+#     But avoid directories, they are not portable.
+#     Copied verbatim.
+#   - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported
+#     by the re-generation script.
+#     Re-generated.
+# - other lines are copied verbatim
+# Substitution (${...:...}), extension ([buildout] extends = ...) and
+# section inheritance (< = ...) are NOT supported (but you should really
+# not need these here).
+[instance-caucased]
+filename = instance-caucased.cfg.jinja2
+md5sum = 31dc85cce6e419235a92c3c7682a89d1
+
+[instance]
+filename = instance.cfg.jinja2
+md5sum = d40bed5ccc457ff7dc99b618bf29b189
diff --git a/software/caucase/instance-caucase-input-schema.json b/software/caucase/instance-caucase-input-schema.json
index 309639180..307bcfc5e 100644
--- a/software/caucase/instance-caucase-input-schema.json
+++ b/software/caucase/instance-caucase-input-schema.json
@@ -4,78 +4,34 @@
   "extends": "./schema-definitions.json#",
   "title": "Input Parameters",
   "properties": {
-    "server-port": {
-      "allOf": [
-        {
-          "$ref": "#/definitions/tcpv4port"
-        },
-        {
-          "title": "http port to use",
-          "description": "Caucase http port to use.",
-          "default": 8009
-        }
-      ]
-    },
-    "server-https-port": {
-      "allOf": [
-        {
-          "$ref": "#/definitions/tcpv4port"
-        },
-        {
-          "title": "https port to use",
-          "description": "Caucase port to use for https connexion.",
-          "default": 8010
-        }
-      ]
+    "base-port": {
+      "title": "Base TCP port",
+      "description": "If 80, caucase will also listen on 443. Otherwise, caucase will listen on port and port + 1.",
+      "type": "integer",
+      "default": 8009
     },
     "external-url": {
-      "title": "External http url",
-      "description": "External http url which point to caucase on http. This url will be added in signed certificate as CRL distribution point URI",
+      "title": "External URL of base port",
+      "description": "When provided, this URL will be added to issued certificate as the CRL distribution point.",
       "type": "string",
       "format": "uri"
     },
-    "ca-subject": {
-      "title": "Subject of CA Certificate",
-      "description": "CA certificate subject as string. The format is: /C=XX/ST=State/L=City/OU=OUnit/O=Company/CN=CA Auth/emailAddress=xx@example.com. Only /CN is mandatory.",
-      "type": "string",
-      "default": "/C=FR/O=Company/CN=SlapOS Certificate Authority/emailAddress=xx@example.com"
-    },
-    "max-request-amount": {
-      "title": "Number of pending csr to accept",
-      "description": "Number of pending csr to accept. If this limit is reached, no more csr will be accepted by the CA.",
+    "service-auto-approve-amount": {
+      "title": "Number of service certificate requests to automatically approve",
+      "description": "Once that number has been reached, a user must validate further requests. Renewals do not count toward this number. Cannot be changed once set.",
       "type": "integer",
-      "default": 10
+      "default": 0
     },
-    "crt-life-time": {
-      "title": "Signed Certificate life time",
-      "description": "The time in seconds before a generated certificate will expire. Default: 365*24*60*60 seconds (1 year)",
+    "user-auto-approve-amount": {
+      "title": "Number of user certificate requests to automatically approve",
+      "description": "Once that number has been reached, a user must validate further requests. Renewals do not count toward this number. Cannot be changed once set.",
       "type": "integer",
-      "default": 31536000,
-      "minimum": 86400
-    },
-    "crl-life-period": {
-      "title": "CRL life time period",
-      "description": "Number of individual certificate validity periods during which the CRL is valid. Default: 1/50.0",
-      "type": "number",
-      "default": 0.2
-    },
-    "ca-life-period": {
-      "title": "CA Certificate life period",
-      "description": "Number of individual certificate validity periods during which the CA certificate is valid. Default: 10",
-      "type": "number",
-      "default": 10
-    },
-    "crt-keep-time": {
-      "title": "Time before cleanup certificate content on CA",
-      "description": "The time in seconds before a generated certificate will be deleted on CA server. Set 0 to never delete. Default: 30*24*60*60 seconds (30 days)",
-      "default": 5184000,
-      "type": "integer"
+      "default": 1
     },
-    "auto-sign-csr-amount": {
-      "title": "Number of CSR to sign automatically",
-      "description": "The number of CSR to sign automatically at startup. Has no effect if there is more than the specified value of csr submitted to caucase. This value should be as lowest as possible",
-      "default": 1,
-      "minimum": 1,
+    "key-length" : {
+      "title": "Key length",
+      "description": "Size, in bits, of the SSL key generated to authenticate users.",
+      "default": 2048,
       "type": "integer"
     }
   }
diff --git a/software/caucase/instance-caucase-output-schema.json b/software/caucase/instance-caucase-output-schema.json
index 98c6dce7e..a317e59fb 100644
--- a/software/caucase/instance-caucase-output-schema.json
+++ b/software/caucase/instance-caucase-output-schema.json
@@ -2,12 +2,8 @@
   "$schema": "http://json-schema.org/draft-04/schema#",
   "description": "Values returned by Caucase instantiation",
   "properties": {
-    "http-url": {
-      "description": "Caucase URL on HTTP",
-      "type": "string"
-    },
-    "https-url": {
-      "description": "Caucase URL on HTTPS",
+    "url": {
+      "description": "Caucase URL",
       "type": "string"
     }
   },
diff --git a/software/caucase/instance-caucased.cfg.jinja2 b/software/caucase/instance-caucased.cfg.jinja2
new file mode 100644
index 000000000..56fd1eb28
--- /dev/null
+++ b/software/caucase/instance-caucased.cfg.jinja2
@@ -0,0 +1,32 @@
+{% import "caucase" as caucase with context %}
+{% set ipv6 = (ipv6_set | list)[0] -%}
+{% set netloc = '[' ~ ipv6 ~ ']:' ~ slapparameter_dict.get('base-port', 8009) -%}
+
+[directory]
+recipe = slapos.cookbook:mkdirectory
+etc = ${buildout:directory}/etc
+promise = ${:etc}/promise
+service-on-watch = ${:etc}/service
+srv = ${buildout:directory}/srv
+
+{{ caucase.caucased(
+  prefix='caucased',
+  buildout_bin_directory=bin_directory,
+  caucased_path='${directory:service-on-watch}/caucased',
+  data_dir='${directory:srv}/caucased',
+  netloc=netloc,
+  service_auto_approve_count=slapparameter_dict.get('service-auto-approve-amount', 0),
+  user_auto_approve_count=slapparameter_dict.get('user-auto-approve-amount', 1),
+  key_len=slapparameter_dict.get('key-length', 2048),
+  promise='${directory:promise}/caucased',
+) }}
+
+[publish]
+recipe = slapos.cookbook:publish.serialised
+url = {{ dumps('http://' ~ netloc) }}
+
+[buildout]
+parts =
+  publish
+  caucased
+  caucased-promise
diff --git a/software/caucase/instance.cfg.jinja2 b/software/caucase/instance.cfg.jinja2
new file mode 100644
index 000000000..0e88c07e2
--- /dev/null
+++ b/software/caucase/instance.cfg.jinja2
@@ -0,0 +1,34 @@
+[buildout]
+parts = switch-softwaretype
+eggs-directory = {{ eggs_directory }}
+develop-eggs-directory = {{ develop_eggs_directory }}
+
+[slap-configuration]
+recipe = slapos.cookbook:slapconfiguration.serialised
+computer = ${slap-connection:computer-id}
+partition = ${slap-connection:partition-id}
+url = ${slap-connection:server-url}
+key = ${slap-connection:key-file}
+cert = ${slap-connection:cert-file}
+
+[context]
+bin-directory = {{ dumps(bin_directory) }}
+caucase-jinja2-library = {{ dumps(caucase_jinja2_library) }}
+instance-caucased = {{ dumps(instance_caucased) }}
+
+[caucased]
+recipe = slapos.recipe.template:jinja2
+template = ${context:instance-caucased}
+rendered = ${buildout:parts-directory}/instance-caucased.cfg
+context =
+  key ipv6_set slap-configuration:ipv6
+  key slapparameter_dict slap-configuration:configuration
+  key bin_directory context:bin-directory
+import-list =
+  file caucase context:caucase-jinja2-library
+
+[switch-softwaretype]
+recipe = slapos.cookbook:switch-softwaretype
+default = caucased:rendered
+# XXX: When will this name finally go away ?
+RootSoftwareInstance = ${:default}
diff --git a/software/caucase/software.cfg b/software/caucase/software.cfg
index 0a812c7b0..27ab39df3 100644
--- a/software/caucase/software.cfg
+++ b/software/caucase/software.cfg
@@ -1,10 +1,29 @@
 [buildout]
 extends =
+  buildout.hash.cfg
   ../../stack/caucase/buildout.cfg
-  ../../stack/slapos.cfg
 
-parts = 
-  slapos-cookbook
-  caucase-extra-eggs
-  instance-caucase
+parts +=
+  instance
+  caucase-eggs
 
+[instance-caucased]
+recipe = slapos.recipe.build:download
+url = ${:_profile_base_location_}/${:filename}
+# XXX: following mode should be the default
+mode = 644
+
+[instance]
+recipe = slapos.recipe.template:jinja2
+# XXX: "template.cfg" is hardcoded in instanciation recipe
+rendered = ${buildout:directory}/template.cfg
+template = ${:_profile_base_location_}/${:filename}
+context =
+  key bin_directory buildout:bin-directory
+  key develop_eggs_directory buildout:develop-eggs-directory
+  key eggs_directory buildout:eggs-directory
+  key caucase_jinja2_library caucase-jinja2-library:target
+  key instance_caucased instance-caucased:target
+
+[versions]
+slapos.recipe.template = 3.0
diff --git a/software/erp5/README.rst b/software/erp5/README.rst
index 084908d89..15aedd301 100644
--- a/software/erp5/README.rst
+++ b/software/erp5/README.rst
@@ -90,6 +90,7 @@ This software release assigns the following port ranges by default:
   balancer              2150-2199
   zope                  2200-*
   jupyter               8888
+  caucase               8890,8891
   ====================  ==========
 
 Non-zope partitions are unique in an ERP5 cluster, so you shouldn't have to
diff --git a/software/erp5/instance-erp5-input-schema.json b/software/erp5/instance-erp5-input-schema.json
index 9c0ee887a..1f4221946 100644
--- a/software/erp5/instance-erp5-input-schema.json
+++ b/software/erp5/instance-erp5-input-schema.json
@@ -350,12 +350,6 @@
           "default": "",
           "type": "string",
           "format": "uri"
-        },
-        "crl-update-periodicity": {
-          "title": "Periodicity of CRL update",
-          "description": "Periodicity of CRL update, in cron format. The CRL will be downloaded from caucase URL and the new content will be saved if there was a change. Everytime a new CRL is writen, Apache reload will be called.",
-          "type": "string",
-          "default": "0 0 * * *"
         }
       },
       "additionalProperties": {
@@ -383,6 +377,34 @@
         }
       },
       "type": "object"
+    },
+    "balancer": {
+      "description": "HTTP(S) load balancer proxy parameters",
+      "properties": {
+        "ssl": {
+          "description": "HTTPS certificate generation parameters",
+          "properties": {
+            "caucase-url":{
+              "title": "Caucase URL",
+              "description": "URL of caucase service to use. If not set, global setting will be used.",
+              "type": "string"
+            },
+            "csr": {
+              "title": "csr",
+              "description": "PEM-encoded certificate signature request to request server certificate with. If not provided, HTTPS will be disabled.",
+              "type": "string"
+            },
+            "max-crl-update-delay": {
+              "title": "Periodicity of CRL update (days)",
+              "description": "CRL will be updated from caucase at least this often.",
+              "type": "number",
+              "default": 1.0
+            }
+          },
+          "type": "object"
+        }
+      },
+      "type": "object"
     }
   }
 }
diff --git a/stack/caucase/README.rst b/stack/caucase/README.rst
new file mode 100644
index 000000000..c58b8b6c5
--- /dev/null
+++ b/stack/caucase/README.rst
@@ -0,0 +1,83 @@
+caucase - CA for Users, CA for SErvices
+
+Slapos integration
+==================
+
+This software release library provides macros to help you integrate caucase
+into your software release.
+
+To use this macros:
+
+ * extend ``stack/caucase/buildout.cfg`` in your Software Release to use it
+ * provide ``caucase-jinja2-library:target`` to the jinja templates where the macros are needed: ``key caucase caucase-jinja2-library:target``
+ * add ``{% import "caucase" as caucase with context %}`` in these jinja templates (note: context needed for ``dumps``, no changes are made to it) 
+
+Software
+--------
+
+This exposes the following sections:
+     
+  - ``caucase-eggs``: Generate scripts to work with certificates using `caucase commands`_. In the software buildout's ``bin`` directory.
+  - ``caucase-jinja2-library``: Provide macros to generate sections that will use scripts created by ``caucase-eggs`` section.
+
+.. _`caucase commands`: https://lab.nexedi.com/nexedi/caucase/blob/master/README.rst#commands
+
+.. note::
+ ``caucase-eggs`` needs to be listed in ``parts=`` of the software buildout or referenced by another installed part. 
+
+.. note:: ``caucase-jinja2-library`` needs to be referenced in an installed section using ``slapos.recipe.template`` from software buildout to make macros available in the the context of instance buildout. From ``software.cfg``:
+  
+  .. code::
+    ini
+    
+    [instance]
+    recipe = slapos.recipe.template:jinja2
+    import-list =
+      file caucase caucase-jinja2-library:target
+
+
+Server
+------
+
+.. topic:: caucased(prefix, caucased_path, data_dir, netloc, service_auto_approve_count=0, key_len=None, promise=None)
+  
+  This macro produces the following sections which you will want to reference sothey get instanciated:
+  
+  - `<prefix>`: Creates `<caucased>` executable file to start `caucased`,
+    and `<data_dir>` directory for its data storage needs.
+    `caucased` will listend on `netloc`, which must be of the format
+    `hostname[:port]`, where hostname may be an IPv4 (ex: `127.0.0.1`), an IPv6
+    (ex: `[::1]`), or a domain name (ex: `localhost`). Port, when provided, must
+    be numeric. If port is not provided, it default to `80`.
+
+   If port is `80`, ``caucased`` will listen on `80` and `443` for given
+   hostname. This is *not* the recommended usage.
+
+   If port is not `80`, ``caucased`` will listen on it *and* its immediate next
+   higher port (ex: ``[::1]:8009`` will listen on both ``[::1]:8009`` and
+   ``[::1]:8010``). This is the recommended usage.
+  
+  - ``<prefix>-promise``: (only produced if ``<promise>`` is not None). Creates an
+    executable at the path given in ``<promise>``, which will attempt to connect to
+    ``caucase`` server, and fail if it detects any anomaly (port not listening,
+    protocol error, certificate discrepancy...).
+
+
+Client
+------
+
+.. topic:: ``rerequest(prefix, buildout_bin_directory, template, csr, key)``
+  
+  - ``<prefix>``: Creates ``<rerequest>`` executable file to run ``caucase-rerequest``.
+  
+  This script allows you to re-issue a CSR using a locally-generated private key.
+
+.. topic:: ``updater(prefix, buildout_bin_directory, updater_path, url, data_dir, crt_path, ca_path, crl_path, key_path=None, on_renew=None, max_sleep=None, mode='service', template_csr_pem=None, openssl=None)``
+
+  - ``<prefix>``: Creates ``<updater>`` executable file to start ``caucase-updater``, and ``<data_dir>`` directory for its data storage needs.
+  
+  ``caucase-updater`` will monitor a key pair, corresponding CA certificate and CRL, and renew them before expiration.
+  
+.. note::
+  You can find more information about any argument mentioned above calling ``<command> --help <argument>``
+
diff --git a/stack/caucase/buildout.cfg b/stack/caucase/buildout.cfg
index 6c8c5abdf..37845ea26 100644
--- a/stack/caucase/buildout.cfg
+++ b/stack/caucase/buildout.cfg
@@ -1,150 +1,37 @@
 [buildout]
-
 extends =
   buildout.hash.cfg
-  ../../component/apache/buildout.cfg
-  ../../component/nginx/buildout.cfg
-  ../../component/curl/buildout.cfg
-  ../../component/dash/buildout.cfg
-  ../../component/openssl/buildout.cfg
-  ../../component/bcrypt/buildout.cfg
-  ../../stack/logrotate/buildout.cfg
-
-
+  ../slapos.cfg
+  ../../component/python-cryptography/buildout.cfg
 parts =
-  instance-caucase
+  slapos-cookbook
 
-[caucase-extra-eggs]
+[caucase-eggs]
 recipe = zc.recipe.egg
-interpreter = python_ca
 eggs =
-  gunicorn # for WSGI HTTP Server
-  futures
-  caucase # certificate authority
-  ${bcrypt:egg}
-# are also required
-  plone.recipe.command
-  collective.recipe.template
-  slapos.toolbox
+  ${python-cryptography:egg}
+  caucase
 scripts =
-  slapos-kill
-  gunicorn
   caucase
-  caucase-cli
-  caucase-cliweb
-
-[template-ca-download-base]
-recipe = hexagonit.recipe.download
-ignore-existing = true
-download-only = true
-url = ${:_profile_base_location_}/${:filename}
-mode = 0644
+  caucase-probe
+  caucase-updater
+  caucased
+  caucased-manage
 
-[template-httpd-auth-conf]
-<= template-ca-download-base
-
-[template-nginx-ca-conf]
-<= template-ca-download-base
+[caucase-jinja2-library-eggs]
+recipe = zc.recipe.egg
+eggs =
+  ${slapos-cookbook:eggs}
+  plone.recipe.command
+  slapos.recipe.template
 
-[template-caucase]
-<= template-ca-download-base
+[caucase-jinja2-library]
+recipe = slapos.recipe.build:download
 url = ${:_profile_base_location_}/${:filename}
-
-[template-authenticated-server]
-recipe = slapos.recipe.template:jinja2
-template = ${:_profile_base_location_}/${:filename}
-rendered = ${buildout:directory}/template-authenticated-server.cfg
-context =
-  key apache_location apache:location
-  key template_logrotate_base template-logrotate-base:rendered
-  raw certificate_request_bin ${buildout:directory}/bin/caucase-cliweb
-  raw curl_executable_location ${curl:location}/bin/curl
-  raw dash_executable_location ${dash:location}/bin/dash
-  raw slapos_kill_bin ${buildout:directory}/bin/slapos-kill
-  raw template_httpd_auth_conf ${template-httpd-auth-conf:location}/${template-httpd-auth-conf:filename}
-  raw openssl_executable_location ${openssl:location}/bin/openssl
-  raw python_bin ${buildout:directory}/bin/${caucase-extra-eggs:interpreter}
-
-[instance-caucase]
-recipe = slapos.recipe.template:jinja2
-template = ${:_profile_base_location_}/${:filename}
-rendered = ${buildout:directory}/template.cfg
-context =
-  key develop_eggs_directory buildout:develop-eggs-directory
-  key eggs_directory buildout:eggs-directory
-  key nginx_location nginx:location
-  key template_logrotate_base template-logrotate-base:rendered
-  raw caucase_template ${template-caucase:location}/${template-caucase:filename}
-  raw curl_executable_location ${curl:location}/bin/curl
-  raw caucase_bin ${buildout:directory}/bin/caucase
-  raw certificate_request_bin ${buildout:directory}/bin/caucase-cliweb
-  raw template_nginx_ca_conf ${template-nginx-ca-conf:location}/${template-nginx-ca-conf:filename}
-  raw dash_executable_location ${dash:location}/bin/dash
-  raw slapos_kill_bin ${buildout:directory}/bin/slapos-kill
-  raw gunicorn_bin ${buildout:directory}/bin/gunicorn
-  raw openssl_executable_location ${openssl:location}/bin/openssl
-  raw python_bin ${buildout:directory}/bin/${caucase-extra-eggs:interpreter}
-
+mode = 0644
+depends = ${caucase-jinja2-library-eggs:eggs}
 
 [versions]
-Flask-User = 0.6.19
-bcrypt = 3.1.3
-caucase = 0.1.4
-futures = 3.1.1
-gunicorn = 19.7.1
-slapos.recipe.template = 4.3
-smmap2 = 2.0.3
-PyJWT = 1.6.4
-
-# Required by:
-# caucase==0.1.4
-Flask-AlchemyDumps = 0.0.10
-
-# Required by:
-# Flask-User==0.6.19
-Flask-Login = 0.4.0
-
-# Required by:
-# Flask-User==0.6.19
-Flask-Mail = 0.9.1
-
-# Required by:
-# Flask-AlchemyDumps==0.0.10
-# Flask-User==0.6.19
-Flask-SQLAlchemy = 2.3.2
-
-# Required by:
-# Flask-AlchemyDumps==0.0.10
-Flask-Script = 2.0.6
-
-# Required by:
-# Flask-User==0.6.19
-Flask-WTF = 0.14.2
-
-# Required by:
-# Flask-AlchemyDumps==0.0.10
-SQLAlchemy = 1.1.15
-
-# Required by:
-# Flask-AlchemyDumps==0.0.10
-Unipath = 1.1
-
-# Required by:
-# Flask-WTF==0.14.2
-WTForms = 2.1
-
-# Required by:
-# Flask-Mail==0.9.1
-blinker = 1.4
-
-# Required by:
-# caucase==0.1.4
+caucase = 0.9.5
 pem = 17.1.0
-
-# Required by:
-# caucase==0.1.4
-pyasn1-modules = 0.0.9
-
-# Required by:
-# Flask-User==0.6.19
-pycryptodome = 3.4.7
+PyJWT = 1.7.1
diff --git a/stack/caucase/buildout.hash.cfg b/stack/caucase/buildout.hash.cfg
index ab512e0a8..db4ab3efb 100644
--- a/stack/caucase/buildout.hash.cfg
+++ b/stack/caucase/buildout.hash.cfg
@@ -13,22 +13,6 @@
 # section inheritance (< = ...) are NOT supported (but you should really
 # not need these here).
 
-[template-httpd-auth-conf]
-filename = template-httpd-auth.conf.in
-md5sum = ea445b0a9b143d12b5700a71ac06293c
-
-[template-nginx-ca-conf]
-filename = ca-nginx.conf.in
-md5sum = d8bebf1629aacffd619541f363687b4a
-
-[template-authenticated-server]
-filename = instance-auth-server.cfg.jinja2.in
-md5sum = c005cfef03a0c2e504fcfa075e59934a
-
-[template-caucase]
-filename = instance-caucase.cfg.jinja2.in
-md5sum = 589a0ad37abc28cf6e34e406e7b83eec
-
-[instance-caucase]
-filename = instance.cfg.in
-md5sum = eb9d2ab646717d123b0472da5194d77f
+[caucase-jinja2-library]
+filename = caucase.jinja2.library
+md5sum = da082c12f547eee3a33e98c535dcbc14
diff --git a/stack/caucase/ca-nginx.conf.in b/stack/caucase/ca-nginx.conf.in
deleted file mode 100644
index be2714c9f..000000000
--- a/stack/caucase/ca-nginx.conf.in
+++ /dev/null
@@ -1,95 +0,0 @@
-worker_processes {{ parameter_dict['workers-processes'] }};
-
-pid {{ parameter_dict['pid-file'] }};
-error_log {{ parameter_dict['error-log'] }};
-
-daemon off;
-
-events {
-  worker_connections 1024;
-  accept_mutex off;
-}
-
-http {
-    # include mime.types;
-    default_type application/octet-stream;
-    access_log {{ parameter_dict['access-log'] }} combined;
-    client_max_body_size 10M;
-    map $http_upgrade $connection_upgrade {
-      default upgrade;
-      ''      close;
-    }
-    sendfile on;
-
-    upstream app_server {
-      # for UNIX domain socket setups
-      server unix:{{ parameter_dict['socket'] }} fail_timeout=0;
-    }
-
-    {% if parameter_dict['cert-file'] and parameter_dict['key-file'] -%}
-    server {
-      listen [{{ parameter_dict['ip'] }}]:{{ parameter_dict['https-port'] }} ssl;
-      server_name _;
-      ssl_certificate     {{ parameter_dict['cert-file'] }};
-      ssl_certificate_key {{ parameter_dict['key-file'] }};
-      ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
-      ssl_ciphers         ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:HIGH:!aNULL:!MD5;
-      ssl_prefer_server_ciphers on;
-      keepalive_timeout 90s;
-      client_body_temp_path {{ parameter_dict['client-body-temp-path'] }};
-      proxy_temp_path {{ parameter_dict['proxy-temp-path'] }};
-      fastcgi_temp_path {{ parameter_dict['fastcgi-temp-path'] }};
-      uwsgi_temp_path {{ parameter_dict['uwsgi-temp-path'] }};
-      scgi_temp_path {{ parameter_dict['scgi-temp-path'] }};
-
-      location / {
-          proxy_redirect off;
-          proxy_set_header   X-Forwarded-Proto $scheme;
-          proxy_set_header   X-Real-IP $remote_addr;
-          proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
-          proxy_set_header   X-Forwarded-Host  $http_host;
-          proxy_set_header   Host $http_host;
-          proxy_set_header   Authorization $http_authorization;
-          proxy_pass_header  Authorization;
-          proxy_connect_timeout 90;
-          proxy_send_timeout    90;
-          proxy_read_timeout    90;
-          send_timeout          90;
-
-          proxy_pass http://app_server;
-      }
-    }
-    {% endif -%}
-
-    server {
-      listen [{{ parameter_dict['ip'] }}]:{{ parameter_dict['port'] }};
-      server_name _;
-      keepalive_timeout 90s;
-      client_body_temp_path {{ parameter_dict['client-body-temp-path'] }};
-      proxy_temp_path {{ parameter_dict['proxy-temp-path'] }};
-      fastcgi_temp_path {{ parameter_dict['fastcgi-temp-path'] }};
-      uwsgi_temp_path {{ parameter_dict['uwsgi-temp-path'] }};
-      scgi_temp_path {{ parameter_dict['scgi-temp-path'] }};
-
-      location ~ ^(/admin|/user) {
-        # http is not used for /admin and /user
-      }
-
-      location / {
-          proxy_redirect off;
-          proxy_set_header   X-Forwarded-Proto $scheme;
-          proxy_set_header   X-Real-IP $remote_addr;
-          proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
-          proxy_set_header   X-Forwarded-Host  $http_host;
-          proxy_set_header   Host $http_host;
-          proxy_set_header   Authorization $http_authorization;
-          proxy_pass_header  Authorization;
-          proxy_connect_timeout 90;
-          proxy_send_timeout    90;
-          proxy_read_timeout    90;
-          send_timeout          90;
-
-          proxy_pass http://app_server;
-      }
-    }
-}
diff --git a/stack/caucase/caucase.jinja2.library b/stack/caucase/caucase.jinja2.library
new file mode 100644
index 000000000..e13971ba5
--- /dev/null
+++ b/stack/caucase/caucase.jinja2.library
@@ -0,0 +1,104 @@
+{% macro caucased(
+  prefix,
+  buildout_bin_directory,
+  caucased_path,
+  data_dir,
+  netloc,
+  service_auto_approve_count=0,
+  user_auto_approve_count=1,
+  key_len=None,
+  backup_dir=None,
+  promise=None
+) -%}
+[{{ prefix }}-directory]
+recipe = slapos.cookbook:mkdirectory
+data-dir = {{ data_dir }}
+mode = 0750
+
+[{{ prefix }}]
+recipe = slapos.cookbook:wrapper
+wrapper-path = {{ caucased_path }}
+command-line = '{{ buildout_bin_directory }}/caucased'
+  --db          '${ {{- prefix }}-directory:data-dir}/caucase.sqlite'
+  --server-key  '${ {{- prefix }}-directory:data-dir}/server.key.pem'
+  --netloc      '{{ netloc }}'
+  {% if key_len %}--key-len '{{ key_len }}' {%- endif %}
+  {% if backup_dir %}--backup-directory {{ backup_dir }} {%- endif %}
+  --service-auto-approve-count '{{ service_auto_approve_count }}'
+  --user-auto-approve-count    '{{ user_auto_approve_count }}'
+  --lock-auto-approve-count
+
+{% if promise -%}
+[{{ prefix }}-promise]
+recipe = slapos.cookbook:wrapper
+wrapper-path = {{ promise }}
+command-line = '{{ buildout_bin_directory }}/caucase-probe' 'http://{{ netloc }}'
+{%- endif %}
+{%- endmacro %}
+
+{% macro updater(
+  prefix,
+  buildout_bin_directory,
+  updater_path,
+  url,
+  data_dir,
+  crt_path,
+  ca_path,
+  crl_path,
+  key_path=None,
+  on_renew=None,
+  max_sleep=None,
+  mode='service',
+  template_csr_pem=None,
+  openssl=None
+) -%}
+[{{ prefix }}-directory]
+recipe = slapos.cookbook:mkdirectory
+data-dir = {{ data_dir }}
+
+{% if template_csr_pem -%}
+[{{ prefix }}-provided-csr-content]
+content = {{ dumps(template_csr_pem) }}
+
+[{{ prefix }}-provided-csr]
+recipe = slapos.recipe.template:jinja2
+mode = 644
+template = inline:{{ '{{ content }}' }}
+rendered = ${ {{- prefix }}-directory:data-dir}/provided.csr.pem
+context = key content {{ prefix }}-provided-csr-content:content
+
+{{   rerequest(
+       prefix=prefix ~ '-csr',
+       buildout_bin_directory='{{ buildout_bin_directory }}',
+       template='{{ "\'$" + prefix }}-provided-csr:rendered}',
+       csr='${:csr}',
+       key=key_path,
+)}}
+{%- else -%}
+[{{ prefix }}-csr]
+recipe = plone.recipe.command
+command = '{{ openssl }}' req -newkey rsa:2048 -batch -new -nodes -subj /CN=example.com -keyout '{{ key_path or crt_path }}' -out '${:csr}'
+{%- endif %}
+csr = ${ {{- prefix }}-directory:data-dir}/good.csr.pem
+
+[{{ prefix }}]
+recipe = slapos.cookbook:wrapper
+wrapper-path = {{ updater_path }}
+command-line = '{{ buildout_bin_directory }}/caucase-updater'
+  --ca-url '{{ url }}'
+  --cas-ca '${ {{- prefix }}-directory:data-dir}/cas.crt.pem'
+  --mode '{{ mode }}'
+  --csr '${ {{- prefix }}-csr:csr}'
+  --crt '{{ crt_path }}'
+  --ca '{{ ca_path }}'
+  --crl '{{ crl_path }}'
+  {% if key_path %}--key '{{ key_path }}' {%- endif %}
+  --on-renew '{{ on_renew }}'
+  {% if max_sleep %}--max-sleep '{{ max_sleep }}' {%- endif %}
+{%- endmacro %}
+
+{% macro rerequest(prefix, buildout_bin_directory, template, csr, key) -%}
+[{{ prefix }}]
+recipe = plone.recipe.command
+command = '{{ buildout_bin_directory }}/caucase-rerequest' --template '{{ template }}' --csr '{{ csr }}' --key '{{ key }}'
+{%- endmacro %}
diff --git a/stack/caucase/instance-auth-server.cfg.jinja2.in b/stack/caucase/instance-auth-server.cfg.jinja2.in
deleted file mode 100644
index 3a3a1b042..000000000
--- a/stack/caucase/instance-auth-server.cfg.jinja2.in
+++ /dev/null
@@ -1,152 +0,0 @@
-[buildout]
-
-extends =
-  {{ template_logrotate_base }}
-
-parts = 
-  authenticated-httpd-server
-
-[authenticated-server-parameters]
-ca-url = 
-common-name = instance@${slap-configuration:instance-title}
-server-port = 8286
-custom-httpd-file = 
-web-directory = ${directory:document-root}
-
-[directory]
-recipe = slapos.cookbook:mkdirectory
-etc = ${buildout:directory}/etc
-bin = ${buildout:directory}/bin
-srv = ${buildout:directory}/srv
-var = ${buildout:directory}/var
-ssl = ${:etc}/ssl
-run = ${:var}/run
-log = ${:var}/log
-scripts = ${:etc}/run
-services = ${:etc}/service
-promises = ${:etc}/promise
-document-root = ${:srv}/private
-
-[certificate-request-base]
-recipe = slapos.cookbook:wrapper
-wrapper-path = ${directory:bin}/request-instance-certificate
-cert-file = ${directory:ssl}/instance.cert.pem
-key-file = ${directory:ssl}/instance.key.pem
-ca-cert = ${directory:ssl}/cacert.pem
-command-line = {{ certificate_request_bin }}
-  --crt-file ${:cert-file}
-  --key-file ${:key-file}
-  --ca-url ${authenticated-server-parameters:ca-url}
-  --ca-crt-file ${:ca-cert}
-  --no-check-certificate
-# XXX - using --no-check-certificate which is insecure with https.
-
-[server-certificate-request]
-recipe = slapos.cookbook:wrapper
-wrapper-path = ${directory:scripts}/request-instance-certificate
-command-line =
-  ${certificate-request-base:wrapper-path}
-  --cn ${authenticated-server-parameters:common-name}
-  --request
-
-[authenticated-httpd-conf-parameter]
-ip = ${slap-configuration:ipv6-random}
-port = ${authenticated-server-parameters:server-port}
-pid-file = ${directory:run}/httpd-auth.pid
-dav-lock = ${directory:var}/DavLockdb
-access-log = ${directory:log}/httpd-auth-access.log
-error-log = ${directory:log}/httpd-auth-error.log
-cert-file = ${certificate-request-base:cert-file}
-key-file = ${certificate-request-base:key-file}
-ca-cert = ${certificate-request-base:ca-cert}
-url = https://[${:ip}]:${:port}
-private = ${authenticated-server-parameters:web-directory}
-httpd-include-file = ${authenticated-server-parameters:custom-httpd-file}
-crl = 
-
-[authenticated-httpd-conf]
-recipe = slapos.recipe.template:jinja2
-template = {{ template_httpd_auth_conf }}
-rendered = ${directory:etc}/httpd-auth.conf
-mode = 0744
-context =
-  section parameter_dict authenticated-httpd-conf-parameter
-
-[authenticated-httpd-graceful]
-recipe = collective.recipe.template
-input = inline:
-  #!{{ dash_executable_location }}
-  kill -USR1 $(cat ${authenticated-httpd-conf-parameter:pid-file})
-
-output = ${directory:scripts}/authenticated-httpd-graceful
-mode = 700
-
-[authenticated-httpd-server]
-recipe = slapos.cookbook:wrapper
-command-line = {{ apache_location }}/bin/httpd -f ${authenticated-httpd-conf:rendered} -DFOREGROUND
-wrapper-path = ${directory:services}/authenticated-httpd-server
-wait-for-files =
-  ${certificate-request-base:cert-file}
-  ${certificate-request-base:key-file}
-  ${certificate-request-base:ca-cert}
-
-url = ${authenticated-httpd-conf-parameter:url}
-
-depends = 
-  ${authenticated-httpd-promise:filename}
-  ${authenticated-httpd-graceful:output}
-  ${server-certificate-request:wrapper-path}
-  ${logrotate-authenticated-httpd:name}
-  ${certificate-renew-cron-entry:name}
-
-[certificate-renew]
-recipe = collective.recipe.template
-input = inline:
-  #!{{ dash_executable_location }}
-
-  d=$({{ openssl_executable_location }} x509 -enddate -noout -in ${certificate-request-base:cert-file} | cut -d'=' -f 2)
-  cert_time=$(date -d "$d" +"%s")
-  now=$(date +"%s")
-  thresold=2592000  # 30*24*60*60  equivalent to one month in seconds
-  remind=$(($cert_time - $now))
-
-  if [ $remind -lt $thresold ]; then
-    exec ${certificate-request-base:wrapper-path} --renew
-    exec ${authenticated-httpd-graceful:output}
-  fi
-
-output = ${directory:bin}/certificate-renew
-mode = 700
-
-[certificate-renew-cron-entry]
-recipe = slapos.cookbook:cron.d
-cron-entries = ${cron:cron-entries}
-name = certificate-auto-renew
-frequency = 5 6 * * 6
-command = ${certificate-renew:output}
-
-[logrotate-authenticated-httpd]
-< = logrotate-entry-base
-name = authenticated-httpd-server
-log = ${authenticated-httpd-conf-parameter:access-log} ${authenticated-httpd-conf-parameter:access-log}
-post = {{ slapos_kill_bin }} --pidfile ${authenticated-httpd-conf-parameter:pid-file} -s USR1
-
-[authenticated-httpd-promise]
-recipe = slapos.cookbook:check_url_available
-path = ${directory:promises}/${:filename}
-filename = authenticated-httpd-is-available
-url = ${authenticated-httpd-conf-parameter:url}
-check-secure = 1
-dash_path = {{ dash_executable_location }}
-curl_path = {{ curl_executable_location }}
-cert-file = ${certificate-request-base:cert-file}
-key-file = ${certificate-request-base:key-file}
-ca-cert-file = ${certificate-request-base:ca-cert}
-
-[slap-configuration]
-recipe = slapos.cookbook:slapconfiguration.serialised
-computer = ${slap-connection:computer-id}
-partition = ${slap-connection:partition-id}
-url = ${slap-connection:server-url}
-key = ${slap-connection:key-file}
-cert = ${slap-connection:cert-file}
diff --git a/stack/caucase/instance-caucase.cfg.jinja2.in b/stack/caucase/instance-caucase.cfg.jinja2.in
deleted file mode 100644
index 6ca981b14..000000000
--- a/stack/caucase/instance-caucase.cfg.jinja2.in
+++ /dev/null
@@ -1,248 +0,0 @@
-{% set part_list = [] -%}
-{% set ipv6 = (ipv6 | list)[0] -%}
-{% set default_subject = '/C=FR/O=Company/CN=SlapOS Certificate Authority/emailAddress=xx@example.com' -%}
-{% if slapparameter_dict is not defined -%}
-{% set slapparameter_dict = {} -%}
-{% endif -%}
-
-[ca-parameters]
-server-port = {{ slapparameter_dict.get('server-port', 8009) }}
-server-https-port = {{ slapparameter_dict.get('server-https-port', 8010) }}
-ipv6 = {{ ipv6 }}
-# Overrite this to set frontend or DNS URL (URL is used as CRL distribution point)
-# Please set http not HTTPS scheme
-crl-external-url = {{ slapparameter_dict.get('external-url', 'http://[${:ipv6}]:${:server-port}') }}
-# /CN=XXX is required and should be unique
-ca-subject = {{ slapparameter_dict.get('ca-subject', default_subject) }}
-# Number of pending csr to accept
-max-request-amount = {{ slapparameter_dict.get('max-request-amount', 10) }}
-# one year (in seconds)
-crt-life-time = {{ slapparameter_dict.get('crt-life-time', 31536000) }}
-# crl-life-period correspond to about one week
-crl-life-period = {{ slapparameter_dict.get('crl-life-period', 0.02) }}
-# ca-life-period = ca-life-period * crt-life-time
-ca-life-period = {{ slapparameter_dict.get('ca-life-period', 10) }}
-# time before clean certificate on CA: 60*24*60*60
-crt-keep-time = {{ slapparameter_dict.get('crt-keep-time', 5184000) }}
-# number of csr to sign automaticaly, minimum value is 1
-{% if int(slapparameter_dict.get('auto-sign-csr-amount', 1)) < 1 -%}
-auto-sign-csr-amount = 1
-{% else -%}
-auto-sign-csr-amount = {{ slapparameter_dict.get('auto-sign-csr-amount', 1) }}
-{% endif -%}
-
-[directory]
-recipe = slapos.cookbook:mkdirectory
-etc = ${buildout:directory}/etc
-bin = ${buildout:directory}/bin
-srv = ${buildout:directory}/srv
-var = ${buildout:directory}/var
-run = ${:var}/run
-log = ${:var}/log
-scripts = ${:etc}/run
-services = ${:etc}/service
-promises = ${:etc}/promise
-ssl=${:etc}/ssl
-ca-dir = ${directory:srv}/ca
-ca-temp = ${:ca-dir}/tmp
-client-body-temp-path = ${:ca-temp}/client_body_temp_path
-proxy-temp-path = ${:ca-temp}/proxy_temp_path
-fastcgi-temp-path = ${:ca-temp}/fastcgi_temp_path
-uwsgi-temp-path = ${:ca-temp}/uwsgi_temp_path
-scgi-temp-path = ${:ca-temp}/scgi_temp_path
-
-[nginx-certificate-request-base]
-recipe = slapos.cookbook:wrapper
-wrapper-path = ${directory:bin}/request-base-certificate
-cert-file = ${ca-nginx-ssl-config:cert}
-key-file = ${ca-nginx-ssl-config:key}
-ca-cert = ${directory:ssl}/cacert.pem
-command-line = {{ certificate_request_bin }}
-  --crt-file ${:cert-file}
-  --key-file ${:key-file}
-  --ca-url http://[${ca-parameters:ipv6}]:${ca-parameters:server-port}
-  --ca-crt-file ${:ca-cert}
-
-[nginx-certificate-request]
-recipe = slapos.cookbook:wrapper
-wrapper-path = ${directory:scripts}/request-server-certificate
-command-line =
-  ${nginx-certificate-request-base:wrapper-path}
-  --cn nginx@certificate.authority
-  --request
-
-[ca-nginx-ssl-config]
-# if ssl certificate is signed write to file so that zero-knowledge can read
-recipe = plone.recipe.command
-command = 
-  if [ -s "${:key}" ] && [ -s "${:cert}" ]; then
-  cat << EOF > ${:output}
-  [ca-nginx-ssl]
-  key=${:key}
-  cert=${:cert}
-  EOF
-  fi
-key = ${directory:ssl}/ca-cert.key
-cert = ${directory:ssl}/ca-cert.crt
-update-command = ${:command}
-output = ${directory:etc}/ca-nginx-ssl.cfg
-stop-on-error = true
-
-[ca-nginx-ssl]
-recipe = slapos.cookbook:zero-knowledge.read
-file-path = ${ca-nginx-ssl-config:output}
-# initials values are empty, the section https (ssl) in nginx config will be skipped
-cert = 
-key = 
-
-[ca-nginx-conf-parameter]
-ip = ${ca-parameters:ipv6}
-port = ${ca-parameters:server-port}
-https-port = ${ca-parameters:server-https-port}
-pid-file = ${directory:run}/nginx-ca.pid
-access-log = ${directory:log}/nginx-ca-access.log
-error-log = ${directory:log}/nginx-ca-error.log
-cert-file = ${ca-nginx-ssl:cert}
-key-file = ${ca-nginx-ssl:key}
-ca-conf = ${caucase-conf:output}
-workers-processes = 1
-client-body-temp-path = ${directory:client-body-temp-path}
-proxy-temp-path = ${directory:proxy-temp-path}
-fastcgi-temp-path = ${directory:fastcgi-temp-path}
-uwsgi-temp-path = ${directory:uwsgi-temp-path}
-scgi-temp-path = ${directory:scgi-temp-path}
-socket = ${caucase-gunicorn:socket}
-
-[ca-nginx-conf]
-recipe = slapos.recipe.template:jinja2
-template = {{ template_nginx_ca_conf }}
-rendered = ${directory:etc}/nginx-ca.conf
-mode = 0700
-context =
-  section parameter_dict ca-nginx-conf-parameter
-
-[caucase-conf]
-recipe = collective.recipe.template
-# Values here are intended to be changed in your instance. override this section
-input = inline:
-  ca-dir ${directory:ca-dir}
-  # enable debug
-  # debug
-  # log-file ${directory:log}/ca-server.log
-  subject ${ca-parameters:ca-subject}
-  max-request-amount ${ca-parameters:max-request-amount}
-  external-url ${ca-parameters:crl-external-url}
-  # one year (in seconds)
-  crt-life-time ${ca-parameters:crt-life-time}
-  # crl-life-period correspond to about one week
-  crl-life-period ${ca-parameters:crl-life-period}
-  # ca-life-time = ca-life-period * crt-life-time
-  ca-life-period ${ca-parameters:ca-life-period}
-  # time before clean certificate on CA: 60*24*60*60
-  crt-keep-time ${ca-parameters:crt-keep-time}
-  # number of csr to sign automaticaly
-  auto-sign-csr-amount ${ca-parameters:auto-sign-csr-amount}
-
-output = ${directory:etc}/ca.conf
-mode = 700
-
-[ca-nginx-graceful]
-recipe = collective.recipe.template
-input = inline:#!{{ dash_executable_location }}
-  kill -HUP "$(cat '${ca-nginx-conf-parameter:pid-file}')"
-
-output = ${directory:scripts}/ca-server-graceful
-mode = 700
-
-[caucase-gunicorn]
-recipe = slapos.cookbook:wrapper
-socket = ${directory:ca-dir}/ng.sock
-command-line =  {{ gunicorn_bin }} caucase.wsgi:app -b unix:${:socket} -e CA_CONFIGURATION_FILE=${caucase-conf:output} --error-logfile ${:log-file} --pid ${:pid-file} --capture-output --timeout 60 --threads 2 --log-level error --preload
-log-file = ${directory:log}/ca-gunicorn-error.log
-pid-file = ${directory:run}/ca-gunicorn.pid
-wrapper-path = ${directory:services}/ca-gunicorn
-
-[caucase-server]
-recipe = slapos.cookbook:wrapper
-command-line = {{ nginx_location }}/sbin/nginx -p ${directory:ca-dir} -c ${ca-nginx-conf:rendered}
-wrapper-path = ${directory:services}/caucase-server
-url = https://[${ca-parameters:ipv6}]:${ca-parameters:server-https-port}
-http-url = ${ca-parameters:crl-external-url}
-depends = 
-  ${nginx-certificate-request:wrapper-path}
-  ${caucase-https-server-promise:filename}
-  ${ca-nginx-graceful:output}
-  ${ca-certificate-renew-cron-entry:name}
-  ${logrotate-ca-nginx:name}
-
-# Disabled to be re-implemented.
-#  ${caucase-server-promise:filename}
-
-[ca-certificate-renew-cron-entry]
-recipe = slapos.cookbook:cron.d
-cron-entries = ${cron:cron-entries}
-name = ca-server-certificate-auto-renew
-# check renew every-week
-time = weekly
-# 2592000 = 30*24*60*60  equivalent to one month in seconds
-command = ${nginx-certificate-request-base:wrapper-path} --renew --threshold 2592000 --on-renew="${ca-nginx-graceful:output}"
-
-[logrotate-ca-nginx]
-< = logrotate-entry-base
-name = caucase-nginx-server
-log = ${ca-nginx-conf-parameter:access-log} ${ca-nginx-conf-parameter:access-log}
-post = {{ slapos_kill_bin }} --pidfile ${ca-nginx-conf-parameter:pid-file} -s USR1
-
-# This promise is disabled as it requires user to take action so the buildout
-# will fail for way too long, and overload master. Please reimplement on a 
-# better way
-#[caucase-server-promise]
-#recipe = slapos.cookbook:check_url_available
-#path = ${directory:promises}/${:filename}
-#filename = caucase-server-listening-on-tcp
-#url = http://[${ca-parameters:ipv6}]:${ca-parameters:server-port}
-#dash_path = {{ dash_executable_location }}
-#curl_path = {{ curl_executable_location }}
-#
-[caucase-https-server-promise]
-recipe = slapos.cookbook:check_url_available
-path = ${directory:promises}/${:filename}
-filename = caucase-server-https-on-${ca-parameters:server-https-port}
-url = https://[${ca-parameters:ipv6}]:${ca-parameters:server-https-port}
-check-secure = 1
-dash_path = {{ dash_executable_location }}
-curl_path = {{ curl_executable_location }}
-
-{% if publish_parameter is defined and publish_parameter == 'yes' -%}
-[publish-connection-parameter]
-recipe = slapos.cookbook:publish.serialised
-http-url = ${caucase-server:http-url}
-https-url = ${caucase-server:url}
-init-user = admin
-{% do part_list.append('publish-connection-parameter') -%}
-{% endif -%}
-
-# Used for ERP5 resiliency or (more probably)
-# webrunner resiliency with erp5 inside.
-[resiliency-exclude-file]
-# Generate rdiff exclude file
-recipe = slapos.recipe.template:jinja2
-mode = 644
-template = inline:
-  ${directory:log}/**
-rendered = ${directory:srv}/exporter.exclude
-{% do part_list.append('resiliency-exclude-file') -%}
-
-[buildout]
-
-extends =
-  {{ template_logrotate_base }}
-
-parts = 
-  caucase-server
-# Complete parts with sections
-  {{ part_list | join('\n  ') }}
-
-eggs-directory = {{ eggs_directory }}
-develop-eggs-directory = {{ develop_eggs_directory }}
-offline = true
diff --git a/stack/caucase/instance.cfg.in b/stack/caucase/instance.cfg.in
deleted file mode 100644
index 2d77cc0e7..000000000
--- a/stack/caucase/instance.cfg.in
+++ /dev/null
@@ -1,43 +0,0 @@
-[buildout]
-parts =
-  switch-softwaretype
-
-eggs-directory = {{ eggs_directory }}
-develop-eggs-directory = {{ develop_eggs_directory }}
-offline = true
-
-[slap-configuration]
-recipe = slapos.cookbook:slapconfiguration.serialised
-computer = ${slap-connection:computer-id}
-partition = ${slap-connection:partition-id}
-url = ${slap-connection:server-url}
-key = ${slap-connection:key-file}
-cert = ${slap-connection:cert-file}
-
-[dynamic-template-caucase]
-recipe = slapos.recipe.template:jinja2
-filename = ${:_buildout_section_name_}.cfg
-rendered = ${buildout:parts-directory}/${:_buildout_section_name_}/${:filename}
-template = {{ caucase_template }}
-extensions = jinja2.ext.do
-extra-context =
-context =
-    key ipv4 slap-configuration:ipv4
-    key ipv6 slap-configuration:ipv6
-    key develop_eggs_directory buildout:develop-eggs-directory
-    key eggs_directory buildout:eggs-directory
-    key slapparameter_dict slap-configuration:configuration
-    raw gunicorn_bin {{ gunicorn_bin }}
-    raw template_logrotate_base {{ template_logrotate_base }}
-    raw certificate_request_bin {{ certificate_request_bin }}
-    raw template_nginx_ca_conf {{ template_nginx_ca_conf }}
-    raw nginx_location {{ nginx_location }}
-    raw slapos_kill_bin {{ slapos_kill_bin }}
-    raw dash_executable_location {{ dash_executable_location }}
-    raw curl_executable_location {{ curl_executable_location }}
-    raw publish_parameter yes
-
-[switch-softwaretype]
-recipe = slapos.cookbook:softwaretype
-default = ${dynamic-template-caucase:rendered}
-
diff --git a/stack/caucase/template-httpd-auth.conf.in b/stack/caucase/template-httpd-auth.conf.in
deleted file mode 100644
index 545a6c95c..000000000
--- a/stack/caucase/template-httpd-auth.conf.in
+++ /dev/null
@@ -1,84 +0,0 @@
-Listen [{{ parameter_dict['ip'] }}]:{{ parameter_dict['port'] }}
-
-LoadModule unixd_module modules/mod_unixd.so
-LoadModule access_compat_module modules/mod_access_compat.so
-LoadModule authz_core_module modules/mod_authz_core.so
-LoadModule authz_host_module modules/mod_authz_host.so
-LoadModule mime_module modules/mod_mime.so
-LoadModule dir_module modules/mod_dir.so
-LoadModule ssl_module modules/mod_ssl.so
-LoadModule alias_module modules/mod_alias.so
-LoadModule autoindex_module modules/mod_autoindex.so
-LoadModule proxy_module      modules/mod_proxy.so
-LoadModule proxy_http_module modules/mod_proxy_http.so
-LoadModule rewrite_module modules/mod_rewrite.so
-LoadModule headers_module modules/mod_headers.so
-LoadModule env_module modules/mod_env.so
-LoadModule setenvif_module modules/mod_setenvif.so
-LoadModule log_config_module modules/mod_log_config.so
-LoadModule dav_module modules/mod_dav.so
-LoadModule dav_fs_module modules/mod_dav_fs.so
-
-ServerAdmin admin@
-TypesConfig conf/mime.types
-AddType application/x-compress .Z
-AddType application/x-gzip .gz .tgz
-
-ServerTokens Prod
-ServerSignature Off
-TraceEnable Off
-
-PidFile "{{ parameter_dict['pid-file'] }}"
-ErrorLog "{{ parameter_dict['error-log'] }}"
-# Default apache log format with request time in microsecond at the end
-LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" combined
-CustomLog "{{ parameter_dict['access-log'] }}" combined
-
-# SSL Configuration
-Define SSLConfigured
-SSLCertificateFile {{ parameter_dict['cert-file'] }}
-SSLCertificateKeyFile {{ parameter_dict['key-file'] }}
-SSLRandomSeed startup builtin
-SSLRandomSeed connect builtin
-SSLRandomSeed startup /dev/urandom 256
-SSLRandomSeed connect builtin
-SSLProtocol all -SSLv2 -SSLv3
-SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:HIGH:!aNULL:!MD5
-SSLHonorCipherOrder on
-
-SSLEngine   On
-
-
-<Directory />
-  Options FollowSymLinks
-  AllowOverride None
-  Allow from all
-</Directory>
-
-<VirtualHost *:{{ parameter_dict['port'] }}>
-
-  DavLockDB {{ parameter_dict['dav-lock'] }}
-
-  SSLVerifyClient require
-  RequestHeader set REMOTE_USER %{SSL_CLIENT_S_DN_CN}s
-  SSLCACertificateFile {{ parameter_dict['ca-cert'] }}
-  {% if parameter_dict['crl'] -%}
-  SSLCARevocationCheck chain
-  SSLCARevocationFile {{ parameter_dict['crl'] }}
-  {%- endif %}
-
-  Alias / {{ parameter_dict['private'] }}/
-  <Directory {{ parameter_dict['private'] }}>
-    DirectoryIndex disabled
-    DAV On
-    Options Indexes FollowSymLinks
-    Order Allow,Deny
-    Allow from all
-  </Directory>
-
-</VirtualHost>
-
-{% if parameter_dict.get('httpd-include-file', '') -%}
-# Custom apache configuration here
-Include {{ parameter_dict['httpd-include-file'] }}
-{% endif -%}
diff --git a/stack/erp5/buildout.cfg b/stack/erp5/buildout.cfg
index e3864630b..fe55aaeb6 100644
--- a/stack/erp5/buildout.cfg
+++ b/stack/erp5/buildout.cfg
@@ -62,7 +62,7 @@ extends =
   ../../component/postfix/buildout.cfg
   ../../component/zbarlight/buildout.cfg
   ../monitor/buildout.cfg
-  ../../software/caucase/software.cfg
+  ../../stack/caucase/buildout.cfg
   ../../software/jupyter/software.cfg
   ../../software/neoppod/software-common.cfg
 # keep neoppod extends last
@@ -149,9 +149,6 @@ extra-ldflags = -Wl,-rpath=${gcc:location}/lib -Wl,-rpath=${gcc:location}/lib64
 [instance-jupyter-notebook]
 rendered = ${buildout:directory}/template-jupyter.cfg
 
-[instance-caucase]
-rendered = ${buildout:directory}/instance-caucase.cfg
-
 [download-base]
 <= download-base-neo
 url = ${:_profile_base_location_}/${:filename}
@@ -233,7 +230,7 @@ context =
     key bin_directory buildout:bin-directory
     key buildout_bin_directory buildout:bin-directory
     key cairo_location cairo:location
-    key caucase_template instance-caucase:rendered
+    key caucase_jinja2_library caucase-jinja2-library:target
     key coreutils_location coreutils:location
     key cups_location cups:location
     key curl_location curl:location
@@ -442,6 +439,7 @@ initialization =
 [eggs]
 <= neoppod
 eggs = ${neoppod:eggs}
+  ${caucase-eggs:eggs}
   ${numpy:egg}
   ${matplotlib:egg}
   ${lxml-python:egg}
@@ -571,6 +569,7 @@ eggs = ${neoppod:eggs}
 # installation of python, which we don't want on an instance
 interpreter = ${buildout:python}
 scripts =
+  ${caucase-eggs:scripts}
   apachedex
   repozo
   runzope
diff --git a/stack/erp5/buildout.hash.cfg b/stack/erp5/buildout.hash.cfg
index fb9630a1e..0304075b7 100644
--- a/stack/erp5/buildout.hash.cfg
+++ b/stack/erp5/buildout.hash.cfg
@@ -66,7 +66,7 @@ md5sum = 0969fbb25b05c02ef3c2d437b2f4e1a0
 
 [template]
 filename = instance.cfg.in
-md5sum = be1466bfce26211e1f0b9e5cee47fe44
+md5sum = 278781b66fc7b208d9090005019abb7a
 
 [monitor-template-dummy]
 filename = dummy.cfg
@@ -74,7 +74,7 @@ md5sum = d41d8cd98f00b204e9800998ecf8427e
 
 [template-erp5]
 filename = instance-erp5.cfg.in
-md5sum = acd108217ff7a1b02b338c01c9c4aa27
+md5sum = 6c463effae65d8269586dfa51bd3f1f1
 
 [template-zeo]
 filename = instance-zeo.cfg.in
@@ -82,11 +82,11 @@ md5sum = d400c3d449ce437a0ded77ee3d5c5df2
 
 [template-zope]
 filename = instance-zope.cfg.in
-md5sum = 1a06ffa9f54e59604d4fedac0f6a99e7
+md5sum = 34b613d297a300d41c59c02ca12e6f84
 
 [template-balancer]
 filename = instance-balancer.cfg.in
-md5sum = 016c56374566b8ea2a1cdb7c8ada3ea7
+md5sum = 7fcedcacb0558e770cbb1c1d63322ea4
 
 [template-haproxy-cfg]
 filename = haproxy.cfg.in
diff --git a/stack/erp5/instance-balancer.cfg.in b/stack/erp5/instance-balancer.cfg.in
index e7adfe61c..2752f1fba 100644
--- a/stack/erp5/instance-balancer.cfg.in
+++ b/stack/erp5/instance-balancer.cfg.in
@@ -1,8 +1,7 @@
+{% import "caucase" as caucase with context %}
 {% set part_list = [] -%}
-{% set ssl_parameter_dict = slapparameter_dict.get('ssl', {}) %}
-{% set caucase_url = slapparameter_dict.get('caucase-url', '') -%}
 {% macro section(name) %}{% do part_list.append(name) %}{{ name }}{% endmacro -%}
-{% set use_ipv6 = slapparameter_dict.get('use-ipv6', False) -%}
+{% set ssl_parameter_dict = slapparameter_dict['ssl'] -%}
 {#
 XXX: This template only supports exactly one IPv4 and (if ipv6 is used) one IPv6
 per partition. No more (undefined result), no less (IndexError).
@@ -21,80 +20,23 @@ per partition. No more (undefined result), no less (IndexError).
 recipe = slapos.recipe.template:jinja2
 mode = 644
 
-[simplefile]
-< = jinja2-template-base
-template = inline:{{ '{{ content }}' }}
-
-{% macro simplefile(section_name, file_path, content, mode='') -%}
-{%   set content_section_name = section_name ~ '-content' -%}
-[{{  content_section_name }}]
-content = {{ dumps(content) }}
-
-[{{  section(section_name) }}]
-< = simplefile
-rendered = {{ file_path }}
-context = key content {{content_section_name}}:content
-mode = {{ mode }}
-{%- endmacro %}
-
-[certificate-request-base]
-recipe = slapos.cookbook:wrapper
-wrapper-path = ${directory:bin}/request-instance-certificate
-command-line = {{ parameter_dict['bin-directory'] }}/caucase-cliweb
-  --crt-file ${apache-conf-ssl:cert}
-  --key-file ${apache-conf-ssl:key}
-  --crl-file ${apache-conf-ssl:crl}
-  --ca-url {{ caucase_url }}
-  --ca-crt-file ${apache-conf-ssl:ca-cert}
-
-{% macro request_cert(name, common_name) -%}
-{% set get_crl_periodicity = slapparameter_dict.get('crl-update-periodicity', 'daily') -%}
-
-[{{ section(name ~ '-certificate-request') }}]
-recipe = slapos.cookbook:wrapper
-wrapper-path = ${directory:services}/request-{{ name }}-certificate
-command-line =
-  ${certificate-request-base:wrapper-path}
-  --cn {{ common_name }}
-  --request
-
-[{{ section(name ~ '-renew-cron-entry') }}]
-recipe = slapos.cookbook:cron.d
-cron-entries = ${cron:cron-entries}
-name = {{ name }}-certificate-auto-renew
-time = weekly
-# 2592000 = 30*24*60*60  equivalent to one month in seconds
-command = ${certificate-request-base:wrapper-path} --renew --threshold 2592000 --on-renew="${apache-graceful:output}"
-
-[{{ section(name ~ '-download-crl') }}]
-# download the crl for the first time
-recipe = plone.recipe.command
-command = 
-  if [ ! -s "${apache-conf-ssl:crl}" ]; then
-    ${certificate-request-base:wrapper-path} --update-crl
-  fi
-update-command = ${:command}
-stop-on-error = true
+{{ caucase.updater(
+     prefix='caucase-updater',
+     buildout_bin_directory=parameter_dict['bin-directory'],
+     updater_path='${directory:services-on-watch}/caucase-updater',
+     url=ssl_parameter_dict['caucase-url'],
+     data_dir='${directory:srv}/caucase-updater',
+     crt_path='${apache-conf-ssl:cert}',
+     ca_path='${apache-conf-ssl:ca-cert}',
+     crl_path='${apache-conf-ssl:crl}',
+     key_path='${apache-conf-ssl:key}',
+     on_renew='${apache-graceful:output}',
+     max_sleep=ssl_parameter_dict.get('max-crl-update-delay', 1.0),
+     template_csr_pem=ssl_parameter_dict.get('csr'),
+     openssl=parameter_dict['openssl'] ~ '/bin/openssl',
+)}}
+{% do section('caucase-updater') -%}
 
-[{{ section(name ~ '-update-crl-cron-entry') }}]
-recipe = slapos.cookbook:cron.d
-cron-entries = ${cron:cron-entries}
-name = {{ name }}-update-crl
-time = {{ get_crl_periodicity }}
-# XXX - Update crl call apache graceful restart, it's not recommended to check crl too often, Apache
-# has an issue with reload and can be frozen and stop responding. Default periodicity time = daily
-command = ${certificate-request-base:wrapper-path} --update-crl --on-crl-update="${apache-graceful:output}"
-{%- endmacro %}
-
-{% if use_ipv6 -%}
-[zope-tunnel-base]
-recipe = slapos.cookbook:ipv4toipv6
-runner-path = ${directory:services}/${:base-name}
-6tunnel-path = {{ parameter_dict['6tunnel'] }}/bin/6tunnel
-shell-path = {{ parameter_dict['dash'] }}/bin/dash
-ipv4 = {{ ipv4 }}
-
-{% endif -%}
 {% set haproxy_dict = {} -%}
 {% set apache_dict = {} -%}
 {% set zope_virtualhost_monster_backend_dict = {} %}
@@ -111,18 +53,7 @@ ipv4 = {{ ipv4 }}
 {%       if webdav -%}
 {%         do has_webdav.append(None) %}
 {%       endif -%}
-{%       if use_ipv6 -%}
-{%         set current_port = next_port() -%}
-[{{ section('zope-tunnel-' ~ current_port) }}]
-< = zope-tunnel-base
-base-name = {{ 'zeo-tunnel-' ~ current_port }}
-ipv4-port = {{ current_port }}
-ipv6-port = {{ zope_address.split(']:')[1] }}
-ipv6 = {{ zope_address.split(']:')[0][1:] }}
-{%         set zope_effective_address = ipv4 ~ ":" ~ current_port -%}
-{%       else -%}
-{%         set zope_effective_address = zope_address -%}
-{%       endif -%}
+{%       set zope_effective_address = zope_address -%}
 {%       do zope_family_address_list.append((zope_effective_address, maxconn, webdav)) -%}
 
 {#       # Generate entries with rewrite rule for test runnners #}
@@ -162,7 +93,7 @@ ipv6 = {{ zope_address.split(']:')[0][1:] }}
 {%     set internal_scheme = 'http' -%}
 {%     set external_scheme = 'https' -%}
 {%   endif -%}
-{%   do apache_dict.__setitem__(family_name, (next_port(), external_scheme, internal_scheme ~ '://' ~ ipv4 ~ ':' ~ haproxy_port ~ backend_path, ssl_authentication)) -%}
+{%   do apache_dict.__setitem__(family_name, (next_port(), external_scheme, internal_scheme ~ '://' ~ ipv4 ~ ':' ~ haproxy_port ~ backend_path, slapparameter_dict['ssl-authentication-dict'].get(family_name, False))) -%}
 {% endfor -%}
 
 [haproxy-cfg-parameter-dict]
@@ -184,8 +115,6 @@ wrapper-path = ${directory:services}/haproxy
 command-line = "{{ parameter_dict['haproxy'] }}/sbin/haproxy" -f "${haproxy-cfg:rendered}"
 hash-files = ${haproxy-cfg:rendered}
 
-{# TODO: build socat and wrap it as "${directory:bin}/haproxy-ctl" to connect to "${haproxy-cfg-parameter-dict:socket-path}" #}
-
 [apache-conf-ssl]
 cert = ${directory:apache-conf}/apache.crt
 key = ${directory:apache-conf}/apache.pem
@@ -203,13 +132,13 @@ access-log = ${directory:log}/apache-access.log
 # Apache 2.4's default value (60 seconds) can be a bit too short
 timeout = 300
 # Basic SSL server configuration
-cert = ${apache-ssl:cert}
-key = ${apache-ssl:key}
+cert = ${apache-conf-ssl:cert}
+key = ${apache-conf-ssl:key}
 cipher =
 ssl-session-cache = ${directory:log}/apache-ssl-session-cache
 # Client x509 auth
-ca-cert = ${apache-ssl-client:cert}
-crl = ${apache-ssl-client:crl}
+ca-cert = ${apache-conf-ssl:ca-cert}
+crl = ${apache-conf-ssl:crl}
 
 [apache-conf]
 < = jinja2-template-base
@@ -227,13 +156,12 @@ wait-for-files =
 
 [apache-graceful]
 recipe = collective.recipe.template
+output = ${directory:bin}/apache-httpd-graceful
+mode = 700
 input = inline:
   #!/bin/sh
   kill -USR1 "$(cat '${apache-conf-parameter-dict:pid-file}')"
 
-output = ${directory:bin}/apache-httpd-graceful
-mode = 700
-
 [{{ section('apache-promise') }}]
 # Check any apache port in ipv4, expect other ports and ipv6 to behave consistently
 recipe = slapos.cookbook:check_port_listening
@@ -253,39 +181,6 @@ recipe = slapos.cookbook:publish.serialised
 
 monitor-base-url = ${monitor-publish-parameters:monitor-base-url}
 
-[apache-ssl]
-{% if ssl_parameter_dict.get('key') -%}
-key = ${apache-ssl-key:rendered}
-cert = ${apache-ssl-cert:rendered}
-{{ simplefile('apache-ssl-key', '${apache-conf-ssl:key}', ssl_parameter_dict['key']) }}
-{{ simplefile('apache-ssl-cert', '${apache-conf-ssl:cert}', ssl_parameter_dict['cert']) }}
-{% elif caucase_url -%}
-key = ${apache-conf-ssl:key}
-cert = ${apache-conf-ssl:cert}
-
-{{ request_cert('erp5', 'instance.apache@erp5') }}
-{% else %}
-recipe = plone.recipe.command
-command = "{{ parameter_dict['openssl'] }}/bin/openssl" req -newkey rsa -batch -new -x509 -days 3650 -nodes -keyout "${:key}" -out "${:cert}"
-key = ${apache-conf-ssl:key}
-cert = ${apache-conf-ssl:cert}
-{%- endif %}
-
-[apache-ssl-client]
-{% if ssl_parameter_dict.get('ca-cert') -%}
-cert = ${apache-ssl-ca:rendered}
-crl = ${apache-ssl-crl:rendered}
-{{ simplefile('apache-ssl-ca', '${apache-conf-ssl:ca-cert}', ssl_parameter_dict['ca-cert']) }}
-{{ simplefile('apache-ssl-crl', '${apache-conf-ssl:crl}', ssl_parameter_dict['crl']) }}
-{% elif caucase_url -%}
-cert = ${apache-conf-ssl:ca-cert}
-crl = ${apache-conf-ssl:crl}
-
-{% else %}
-cert =
-crl =
-{%- endif %}
-
 [{{ section('logrotate-apache') }}]
 < = logrotate-entry-base
 name = apache
@@ -299,15 +194,11 @@ bin = ${buildout:directory}/bin
 etc = ${buildout:directory}/etc
 promise = ${:etc}/promise
 services = ${:etc}/run
+services-on-watch = ${:etc}/service
 var = ${buildout:directory}/var
 run = ${:var}/run
 log = ${:var}/log
-ca-dir = ${buildout:directory}/srv/ssl
-requests = ${:ca-dir}/requests
-private = ${:ca-dir}/private
-certs = ${:ca-dir}/certs
-newcerts = ${:ca-dir}/newcerts
-crl = ${:ca-dir}/crl
+srv = ${buildout:directory}/srv
 apachedex = ${monitor-directory:private}/apachedex
 
 [{{ section('resiliency-exclude-file') }}]
diff --git a/stack/erp5/instance-erp5.cfg.in b/stack/erp5/instance-erp5.cfg.in
index c0b44ebeb..1a9091547 100644
--- a/stack/erp5/instance-erp5.cfg.in
+++ b/stack/erp5/instance-erp5.cfg.in
@@ -1,4 +1,5 @@
 {% import "root_common" as root_common with context -%}
+{% import "caucase" as caucase with context %}
 {% set frontend_dict = slapparameter_dict.get('frontend', {}) -%}
 {% set has_frontend = frontend_dict.get('software-url', '') != '' -%}
 {% set site_id = slapparameter_dict.get('site-id', 'erp5') -%}
@@ -19,13 +20,13 @@
 {%   set test_runner_enabled = mariadb_test_database_amount > 0 %}
 {% endif -%}
 {% set monitor_base_url_dict = {} -%}
-{% set caucase_url = slapparameter_dict.get('caucase', {}).pop('url', '') -%}
 {% set monitor_dict = slapparameter_dict.get('monitor', {}) %}
-{% set crl_update_period = slapparameter_dict.get('caucase', {}).pop('crl-update-periodicity', 'daily') -%}
+{% set use_ipv6 = slapparameter_dict.get('use-ipv6', False) -%}
 [request-common]
 <= request-common-base
 config-use-ipv6 = {{ dumps(slapparameter_dict.get('use-ipv6', False)) }}
 config-computer-memory-percent-threshold = {{ dumps(monitor_dict.get('computer-memory-percent-threshold', 80)) }}
+{% set caucase_url = slapparameter_dict.get('caucase', {}).pop('url', '') -%}
 
 {% macro request(name, software_type, config_key, config, ret={'url': True}, key_config={}) -%}
 {% do config.update(slapparameter_dict.get(config_key, {})) -%}
@@ -53,6 +54,44 @@ config-{{ k }} = {{ '${' ~ v ~ '}' }}
 config-name = {{ name }}
 {% endmacro -%}
 
+[directory]
+recipe = slapos.cookbook:mkdirectory
+etc = ${buildout:directory}/etc
+promise = ${:etc}/promise
+service-on-watch = ${:etc}/service
+srv = ${buildout:directory}/srv
+backup-caucased = ${:srv}/backup/caucased
+
+{% set caucase_dict = slapparameter_dict.get('caucase', {}) -%}
+{% set caucase_url = caucase_dict.get('url') -%}
+{% if not caucase_url -%}
+{%   if use_ipv6 -%}
+{%     set caucase_host = '[' ~ (ipv6_set | list)[0] ~ ']' %}
+{%-  else -%}
+{%     set caucase_host = (ipv4_set | list)[0] %}
+{%-  endif %}
+{%   set caucase_port = caucase_dict.get('base-port', 8890) -%}
+{%   set caucase_netloc = caucase_host ~ ':' ~ caucase_port -%}
+{%   set caucase_url = 'http://' ~ caucase_netloc -%}
+{{   caucase.caucased(
+       prefix='caucased',
+       buildout_bin_directory=bin_directory,
+       caucased_path='${directory:service-on-watch}/caucased',
+       backup_dir='${directory:backup-caucased}',
+       data_dir='${directory:srv}/caucased',
+       netloc=caucase_netloc,
+       service_auto_approve_count=caucase_dict.get('service-auto-approve-amount', 1),
+       user_auto_approve_count=caucase_dict.get('user-auto-approve-amount', 0),
+       key_len=caucase_dict.get('key-length', 2048),
+       promise='${directory:promise}/caucased',
+)}}
+{% do root_common.section('caucased') -%}
+{% do root_common.section('caucased-promise') -%}
+{% endif -%}
+{% do publish_dict.__setitem__('caucase-http-url', caucase_url) -%}
+{% set balancer_dict = slapparameter_dict.get('balancer', {}) -%}
+{% do balancer_dict.setdefault('ssl', {}).setdefault('caucase-url', caucase_url) -%}
+
 {{ request('memcached-persistent', 'kumofs', 'kumofs', {'tcpv4-port': 2000}, {'url': True, 'monitor-base-url': False}, key_config={'monitor-passwd': 'monitor-htpasswd:passwd'}) }}
 {{ request('memcached-volatile', 'kumofs', 'memcached', {'tcpv4-port': 2010, 'ram-storage-size': 64}, {'url': True, 'monitor-base-url': False}, key_config={'monitor-passwd': 'monitor-htpasswd:passwd'}) }}
 {{ request('mariadb', 'mariadb', 'mariadb', {'tcpv4-port': 2099, 'max-slowqueries-threshold': monitor_dict.get('max-slowqueries-threshold', 1000), 'slowest-query-threshold': monitor_dict.get('slowest-query-threshold', ''), 'test-database-amount': test_runner_total_database_count}, {'database-list': True, 'test-database-list': True, 'monitor-base-url': False}, key_config={'monitor-passwd': 'monitor-htpasswd:passwd'}) }}
@@ -64,14 +103,6 @@ config-name = {{ name }}
 connection-url = smtp://127.0.0.2:0/
 {%- endif %}
 
-{% if caucase_url -%}
-{%   do publish_dict.__setitem__('caucase-http-url', caucase_url) -%}
-[request-caucase]
-connection-http-url = {{ caucase_url }}
-{%- else %}
-{{ request('caucase', 'caucase', 'caucase', {'server-port': 8890, 'server-https-port': 8891, 'auto-sign-csr-amount': 2}, {'http-url': True, 'https-url': False}) }}
-{% endif -%}
-
 {# ZODB -#}
 {% set zodb_dict = {} -%}
 {% set storage_dict = {} -%}
@@ -124,8 +155,8 @@ return =
 {% endif -%}
 config-bt5 = {{ dumps(slapparameter_dict.get('bt5', bt5_default_list)) }}
 config-bt5-repository-url = {{ dumps(slapparameter_dict.get('bt5-repository-url', local_bt5_repository)) }}
-config-caucase-url = ${request-caucase:connection-http-url}
 config-cloudooo-url = {{ dumps(slapparameter_dict.get('cloudooo-url', default_cloudooo_url)) }}
+config-caucase-url = {{ dumps(caucase_url) }}
 config-deadlock-debugger-password = ${publish-early:deadlock-debugger-password}
 config-developer-list = {{ dumps(slapparameter_dict.get('developer-list', [inituser_login])) }}
 config-saucelabs-dict = {{ dumps(slapparameter_dict.get('saucelabs-dict', {})) }}
@@ -242,7 +273,6 @@ config-url = ${request-jupyter:connection-url}
 {%   endif -%}
 {%- endif %}
 
-{% set balancer_dict = slapparameter_dict.get('balancer', {}) -%}
 [request-balancer]
 <= request-common
 name = balancer
@@ -258,7 +288,6 @@ return =
   {% endif %}
 {% endfor -%}
 {% do monitor_base_url_dict.__setitem__('request-balancer', '${' ~ 'request-balancer' ~ ':connection-monitor-base-url}') -%}
-
 config-zope-family-dict = {{ dumps(zope_family_parameter_dict) }}
 config-tcpv4-port = {{ dumps(balancer_dict.get('tcpv4-port', 2150)) }}
 {% for zope_section_id, name in zope_address_list_id_dict.items() -%}
@@ -269,11 +298,9 @@ config-{{ name }}-test-runner-address-list = {{ ' ${' ~ zope_section_id ~ ':conn
 {% endfor -%}
 # XXX: should those really be same for all families ?
 config-haproxy-server-check-path = {{ dumps(balancer_dict.get('haproxy-server-check-path', '/') % {'site-id': site_id}) }}
-config-ssl = {{ dumps(balancer_dict.get('ssl', {})) }}
 config-monitor-passwd = ${monitor-htpasswd:passwd}
+config-ssl = {{ dumps(balancer_dict['ssl']) }}
 config-name = ${:name}
-config-caucase-url = ${request-caucase:connection-http-url}
-config-crl-update-periodicity = {{ crl_update_period }}
 config-backend-path-dict = {{ dumps(zope_backend_path_dict) }}
 config-ssl-authentication-dict = {{ dumps(ssl_authentication_dict) }}
 config-apachedex-promise-threshold = {{ dumps(monitor_dict.get('apachedex-promise-threshold', 70)) }}
@@ -401,4 +428,3 @@ password = ${monitor-htpasswd:passwd}
 {% for key, value in monitor_base_url_dict.items() -%}
 {{ key }} = {{ value }}
 {% endfor %}
-
diff --git a/stack/erp5/instance-zope.cfg.in b/stack/erp5/instance-zope.cfg.in
index 7b0f81761..3dbfe2815 100644
--- a/stack/erp5/instance-zope.cfg.in
+++ b/stack/erp5/instance-zope.cfg.in
@@ -55,23 +55,12 @@ environment +=
   TZ={{ slapparameter_dict['timezone'] }}
   MATPLOTLIBRC={{ parameter_dict['matplotlibrc'] }}
   INSTANCE_HOME=${:instance-home}
+  CAUCASE={{ slapparameter_dict['caucase-url'] }}
 {% if slapparameter_dict.get('wendelin-core-zblk-fmt') %}
   WENDELIN_CORE_ZBLK_FMT={{ slapparameter_dict['wendelin-core-zblk-fmt'] }}
 {% endif %}
   ${:environment-extra}
 
-[test-certificate-authority]
-recipe = slapos.cookbook:certificate_authority
-openssl-binary = ${binary-link:target-directory}/openssl
-ca-dir = ${directory:test-ca-dir}
-requests-directory = ${directory:test-ca-requests}
-wrapper = ${directory:services}/test-ca
-ca-private = ${directory:test-ca-private}
-ca-certs = ${directory:test-ca-certs}
-ca-newcerts = ${directory:test-ca-newcerts}
-ca-crl = ${directory:test-ca-crl}
-
-
 [directory]
 recipe = slapos.cookbook:mkdirectory
 bin = ${buildout:directory}/bin
@@ -92,22 +81,11 @@ run = ${:var}/run
 services = ${:etc}/run
 service-on-watch = ${:etc}/service
 srv = ${buildout:directory}/srv
+ca-dir = ${:srv}/ssl
 tmp = ${buildout:directory}/tmp
 var = ${buildout:directory}/var
 promises = ${:etc}/promise
 unit-test-path = ${:srv}/test-instance/unit_test
-test-ca-dir = ${:srv}/test-ca
-test-ca-requests = ${:test-ca-dir}/requests
-test-ca-private = ${:test-ca-dir}/private
-test-ca-certs = ${:test-ca-dir}/certs
-test-ca-newcerts = ${:test-ca-dir}/newcerts
-test-ca-crl = ${:test-ca-dir}/crl
-ca-dir = ${:srv}/ca
-ca-requests = ${:ca-dir}/requests
-ca-private = ${:ca-dir}/private
-ca-certs = ${:ca-dir}/certs
-ca-newcerts = ${:ca-dir}/newcerts
-ca-crl = ${:ca-dir}/crl
 
 # Used for ERP5 resiliency or (more probably)
 # webrunner resiliency with erp5 inside.
@@ -130,20 +108,6 @@ recipe = slapos.cookbook:symbolic.link
 target-directory = ${directory:bin}
 link-binary = {{ dumps(parameter_dict['link-binary']) }}
 
-[certificate-authority-common]
-requests-directory = ${directory:ca-requests}
-ca-dir = ${directory:ca-dir}
-ca-private = ${directory:ca-private}
-ca-certs = ${directory:ca-certs}
-ca-newcerts = ${directory:ca-newcerts}
-ca-crl = ${directory:ca-crl}
-
-[{{ section('certificate-authority') }}]
-< = certificate-authority-common
-recipe = slapos.cookbook:certificate_authority
-openssl-binary = ${binary-link:target-directory}/openssl
-wrapper = ${directory:services}/ca
-
 {% if use_ipv6 -%}
 {%   set ipv6 = (ipv6_set | list)[0] -%}
 
@@ -479,8 +443,8 @@ wrapper-path = ${buildout:bin-directory}/runTestSuite
 < = run-common
 environment-extra =
   REAL_INSTANCE_HOME=${:instance-home}
-  OPENSSL_BINARY=${test-certificate-authority:openssl-binary}
-  TEST_CA_PATH=${test-certificate-authority:ca-dir}
+  OPENSSL_BINARY={{ openssl_bin }}/openssl
+  TEST_CA_PATH=${directory:ca-dir}
 instance-home = ${directory:unit-test-path}
 wrapper-path = ${directory:bin}/${:command-name}.real
 command-line =
@@ -561,5 +525,5 @@ extends =
   {{ logrotate_cfg }}
   {{ parameter_dict['template-monitor'] }}
 parts +=
-  {{ part_list | join('\n  ') }}
+  {{ '\n  '.join(part_list) }}
   publish
diff --git a/stack/erp5/instance.cfg.in b/stack/erp5/instance.cfg.in
index 741fec2ed..9675b68ef 100644
--- a/stack/erp5/instance.cfg.in
+++ b/stack/erp5/instance.cfg.in
@@ -1,7 +1,6 @@
 [buildout]
 extends =
   {{ instance_common_cfg }}
-  {{ caucase_template }}
 
 [jinja2-template-base]
 mode = 644
@@ -35,6 +34,10 @@ jupyter-enable-default = {{ jupyter_enable_default }}
 local-bt5-repository = {{ ' '.join(local_bt5_repository.split()) }}
 template-monitor = {{ dumps(template_monitor) }}
 
+[context]
+root-common = {{ root_common }}
+caucase-jinja2-library = {{ caucase_jinja2_library }}
+
 [dynamic-template-erp5]
 <= jinja2-template-base
 template = {{ template_erp5 }}
@@ -47,7 +50,8 @@ extra-context =
     key openssl_location :openssl-location
     import urlparse urlparse
 import-list =
-    rawfile root_common {{ root_common }}
+    file root_common context:root-common
+    file caucase context:caucase-jinja2-library
 
 openssl-location = {{ openssl_location }}
 
@@ -70,6 +74,8 @@ filename = instance-balancer.cfg
 extra-context =
     section parameter_dict dynamic-template-balancer-parameters
     import itertools itertools
+import-list =
+    file caucase context:caucase-jinja2-library
 
 [dynamic-template-zeo-parameters]
 <= default-dynamic-template-parameters
@@ -112,6 +118,7 @@ extra-context =
     import urlparse urlparse
     import hashlib hashlib
     import itertools itertools
+    raw openssl_bin {{ openssl_location}}/bin
 
 [dynamic-template-kumofs-parameters]
 <= default-dynamic-template-parameters
-- 
2.30.9