URL: https://github.com/freeipa/freeipa/pull/534
Author: tiran
 Title: #534: Move csrgen templates into ipaclient package
Action: synchronized

To pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/534/head:pr534
git checkout pr534
From 651ea386f8b2e751a5a8e0932a1718c3b6fac162 Mon Sep 17 00:00:00 2001
From: Christian Heimes <chei...@redhat.com>
Date: Thu, 2 Mar 2017 16:09:53 +0100
Subject: [PATCH 1/2] Move csrgen templates into ipaclient package

csrgen broke packaging of ipaclient for PyPI. All csrgen related
resources are now package data of ipaclient package. Package data is
accessed with Jinja's PackageLoader() or through pkg_resources.

https://pagure.io/freeipa/issue/6714

Signed-off-by: Christian Heimes <chei...@redhat.com>
---
 configure.ac                                       |  1 -
 freeipa.spec.in                                    | 13 ++++----
 install/share/Makefile.am                          |  1 -
 install/share/csrgen/Makefile.am                   | 35 ----------------------
 .../share/csrgen/profiles/caIPAserviceCert.json    | 15 ----------
 install/share/csrgen/profiles/userCert.json        | 15 ----------
 install/share/csrgen/rules/dataDNS.json            | 15 ----------
 install/share/csrgen/rules/dataEmail.json          | 15 ----------
 install/share/csrgen/rules/dataHostCN.json         | 15 ----------
 install/share/csrgen/rules/dataSubjectBase.json    | 15 ----------
 install/share/csrgen/rules/dataUsernameCN.json     | 15 ----------
 install/share/csrgen/rules/syntaxSAN.json          | 15 ----------
 install/share/csrgen/rules/syntaxSubject.json      | 16 ----------
 install/share/csrgen/templates/certutil_base.tmpl  | 11 -------
 install/share/csrgen/templates/openssl_base.tmpl   | 35 ----------------------
 install/share/csrgen/templates/openssl_macros.tmpl | 29 ------------------
 ipaclient/csrgen.py                                | 21 +++++++++----
 ipaclient/csrgen/profiles/caIPAserviceCert.json    | 15 ++++++++++
 ipaclient/csrgen/profiles/userCert.json            | 15 ++++++++++
 ipaclient/csrgen/rules/dataDNS.json                | 15 ++++++++++
 ipaclient/csrgen/rules/dataEmail.json              | 15 ++++++++++
 ipaclient/csrgen/rules/dataHostCN.json             | 15 ++++++++++
 ipaclient/csrgen/rules/dataSubjectBase.json        | 15 ++++++++++
 ipaclient/csrgen/rules/dataUsernameCN.json         | 15 ++++++++++
 ipaclient/csrgen/rules/syntaxSAN.json              | 15 ++++++++++
 ipaclient/csrgen/rules/syntaxSubject.json          | 16 ++++++++++
 ipaclient/csrgen/templates/certutil_base.tmpl      | 11 +++++++
 ipaclient/csrgen/templates/openssl_base.tmpl       | 35 ++++++++++++++++++++++
 ipaclient/csrgen/templates/openssl_macros.tmpl     | 29 ++++++++++++++++++
 ipaclient/setup.py                                 | 10 ++++++-
 ipaplatform/base/paths.py                          |  1 -
 31 files changed, 241 insertions(+), 263 deletions(-)
 delete mode 100644 install/share/csrgen/Makefile.am
 delete mode 100644 install/share/csrgen/profiles/caIPAserviceCert.json
 delete mode 100644 install/share/csrgen/profiles/userCert.json
 delete mode 100644 install/share/csrgen/rules/dataDNS.json
 delete mode 100644 install/share/csrgen/rules/dataEmail.json
 delete mode 100644 install/share/csrgen/rules/dataHostCN.json
 delete mode 100644 install/share/csrgen/rules/dataSubjectBase.json
 delete mode 100644 install/share/csrgen/rules/dataUsernameCN.json
 delete mode 100644 install/share/csrgen/rules/syntaxSAN.json
 delete mode 100644 install/share/csrgen/rules/syntaxSubject.json
 delete mode 100644 install/share/csrgen/templates/certutil_base.tmpl
 delete mode 100644 install/share/csrgen/templates/openssl_base.tmpl
 delete mode 100644 install/share/csrgen/templates/openssl_macros.tmpl
 create mode 100644 ipaclient/csrgen/profiles/caIPAserviceCert.json
 create mode 100644 ipaclient/csrgen/profiles/userCert.json
 create mode 100644 ipaclient/csrgen/rules/dataDNS.json
 create mode 100644 ipaclient/csrgen/rules/dataEmail.json
 create mode 100644 ipaclient/csrgen/rules/dataHostCN.json
 create mode 100644 ipaclient/csrgen/rules/dataSubjectBase.json
 create mode 100644 ipaclient/csrgen/rules/dataUsernameCN.json
 create mode 100644 ipaclient/csrgen/rules/syntaxSAN.json
 create mode 100644 ipaclient/csrgen/rules/syntaxSubject.json
 create mode 100644 ipaclient/csrgen/templates/certutil_base.tmpl
 create mode 100644 ipaclient/csrgen/templates/openssl_base.tmpl
 create mode 100644 ipaclient/csrgen/templates/openssl_macros.tmpl

diff --git a/configure.ac b/configure.ac
index 31bfa8a..4a3ba15 100644
--- a/configure.ac
+++ b/configure.ac
@@ -463,7 +463,6 @@ AC_CONFIG_FILES([
     install/share/Makefile
     install/share/advise/Makefile
     install/share/advise/legacy/Makefile
-    install/share/csrgen/Makefile
     install/share/profiles/Makefile
     install/share/schema.d/Makefile
     install/ui/Makefile
diff --git a/freeipa.spec.in b/freeipa.spec.in
index d8602c9..2eadb7c 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -1229,13 +1229,6 @@ fi
 %{_usr}/share/ipa/advise/legacy/*.template
 %dir %{_usr}/share/ipa/profiles
 %{_usr}/share/ipa/profiles/*.cfg
-%dir %{_usr}/share/ipa/csrgen
-%dir %{_usr}/share/ipa/csrgen/templates
-%{_usr}/share/ipa/csrgen/templates/*.tmpl
-%dir %{_usr}/share/ipa/csrgen/profiles
-%{_usr}/share/ipa/csrgen/profiles/*.json
-%dir %{_usr}/share/ipa/csrgen/rules
-%{_usr}/share/ipa/csrgen/rules/*.json
 %dir %{_usr}/share/ipa/html
 %{_usr}/share/ipa/html/ffconfig.js
 %{_usr}/share/ipa/html/ffconfig_page.js
@@ -1362,6 +1355,9 @@ fi
 %{python_sitelib}/ipaclient/plugins/*.py*
 %{python_sitelib}/ipaclient/remote_plugins/*.py*
 %{python_sitelib}/ipaclient/remote_plugins/2_*/*.py*
+%{python_sitelib}/ipaclient/csrgen/profiles/*.json
+%{python_sitelib}/ipaclient/csrgen/rules/*.json
+%{python_sitelib}/ipaclient/csrgen/templates/*.tmpl
 %{python_sitelib}/ipaclient-*.egg-info
 
 
@@ -1382,6 +1378,9 @@ fi
 %{python3_sitelib}/ipaclient/remote_plugins/__pycache__/*.py*
 %{python3_sitelib}/ipaclient/remote_plugins/2_*/*.py
 %{python3_sitelib}/ipaclient/remote_plugins/2_*/__pycache__/*.py*
+%{python3_sitelib}/ipaclient/csrgen/profiles/*.json
+%{python3_sitelib}/ipaclient/csrgen/rules/*.json
+%{python3_sitelib}/ipaclient/csrgen/templates/*.tmpl
 %{python3_sitelib}/ipaclient-*.egg-info
 
 %endif # with_python3
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index bbf6ce1..1e8f0d5 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -2,7 +2,6 @@ NULL =
 
 SUBDIRS =  				\
 	advise				\
-	csrgen				\
 	profiles			\
 	schema.d			\
 	$(NULL)
diff --git a/install/share/csrgen/Makefile.am b/install/share/csrgen/Makefile.am
deleted file mode 100644
index 12c62c4..0000000
--- a/install/share/csrgen/Makefile.am
+++ /dev/null
@@ -1,35 +0,0 @@
-NULL =
-
-profiledir = $(IPA_DATA_DIR)/csrgen/profiles
-profile_DATA =				\
-	profiles/caIPAserviceCert.json	\
-	profiles/userCert.json		\
-	$(NULL)
-
-ruledir = $(IPA_DATA_DIR)/csrgen/rules
-rule_DATA =				\
-	rules/dataDNS.json		\
-	rules/dataEmail.json		\
-	rules/dataHostCN.json		\
-	rules/dataUsernameCN.json	\
-	rules/dataSubjectBase.json	\
-	rules/syntaxSAN.json		\
-	rules/syntaxSubject.json	\
-	$(NULL)
-
-templatedir = $(IPA_DATA_DIR)/csrgen/templates
-template_DATA =			\
-	templates/certutil_base.tmpl	\
-	templates/openssl_base.tmpl	\
-	templates/openssl_macros.tmpl	\
-	$(NULL)
-
-EXTRA_DIST =				\
-	$(profile_DATA)			\
-	$(rule_DATA)			\
-	$(template_DATA)		\
-	$(NULL)
-
-MAINTAINERCLEANFILES =			\
-	*~				\
-	Makefile.in
diff --git a/install/share/csrgen/profiles/caIPAserviceCert.json b/install/share/csrgen/profiles/caIPAserviceCert.json
deleted file mode 100644
index 114d2ff..0000000
--- a/install/share/csrgen/profiles/caIPAserviceCert.json
+++ /dev/null
@@ -1,15 +0,0 @@
-[
-    {
-        "syntax": "syntaxSubject",
-        "data": [
-            "dataHostCN",
-            "dataSubjectBase"
-        ]
-    },
-    {
-        "syntax": "syntaxSAN",
-        "data": [
-            "dataDNS"
-        ]
-    }
-]
diff --git a/install/share/csrgen/profiles/userCert.json b/install/share/csrgen/profiles/userCert.json
deleted file mode 100644
index d6cf5cf..0000000
--- a/install/share/csrgen/profiles/userCert.json
+++ /dev/null
@@ -1,15 +0,0 @@
-[
-    {
-        "syntax": "syntaxSubject",
-        "data": [
-            "dataUsernameCN",
-            "dataSubjectBase"
-        ]
-    },
-    {
-        "syntax": "syntaxSAN",
-        "data": [
-            "dataEmail"
-        ]
-    }
-]
diff --git a/install/share/csrgen/rules/dataDNS.json b/install/share/csrgen/rules/dataDNS.json
deleted file mode 100644
index 2663f11..0000000
--- a/install/share/csrgen/rules/dataDNS.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "DNS = {{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}"
-    },
-    {
-      "helper": "certutil",
-      "template": "dns:{{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]|quote}}"
-    }
-  ],
-  "options": {
-    "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]"
-  }
-}
diff --git a/install/share/csrgen/rules/dataEmail.json b/install/share/csrgen/rules/dataEmail.json
deleted file mode 100644
index 2eae9fb..0000000
--- a/install/share/csrgen/rules/dataEmail.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "email = {{subject.mail.0}}"
-    },
-    {
-      "helper": "certutil",
-      "template": "email:{{subject.mail.0|quote}}"
-    }
-  ],
-  "options": {
-    "data_source": "subject.mail.0"
-  }
-}
diff --git a/install/share/csrgen/rules/dataHostCN.json b/install/share/csrgen/rules/dataHostCN.json
deleted file mode 100644
index 5c415bb..0000000
--- a/install/share/csrgen/rules/dataHostCN.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}"
-    },
-    {
-      "helper": "certutil",
-      "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]|quote}}"
-    }
-  ],
-  "options": {
-    "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]"
-  }
-}
diff --git a/install/share/csrgen/rules/dataSubjectBase.json b/install/share/csrgen/rules/dataSubjectBase.json
deleted file mode 100644
index 309dfb1..0000000
--- a/install/share/csrgen/rules/dataSubjectBase.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "{{config.ipacertificatesubjectbase.0}}"
-    },
-    {
-      "helper": "certutil",
-      "template": "{{config.ipacertificatesubjectbase.0|quote}}"
-    }
-  ],
-  "options": {
-    "data_source": "config.ipacertificatesubjectbase.0"
-  }
-}
diff --git a/install/share/csrgen/rules/dataUsernameCN.json b/install/share/csrgen/rules/dataUsernameCN.json
deleted file mode 100644
index 37e7e01..0000000
--- a/install/share/csrgen/rules/dataUsernameCN.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "CN={{subject.uid.0}}"
-    },
-    {
-      "helper": "certutil",
-      "template": "CN={{subject.uid.0|quote}}"
-    }
-  ],
-  "options": {
-    "data_source": "subject.uid.0"
-  }
-}
diff --git a/install/share/csrgen/rules/syntaxSAN.json b/install/share/csrgen/rules/syntaxSAN.json
deleted file mode 100644
index 122eb12..0000000
--- a/install/share/csrgen/rules/syntaxSAN.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "subjectAltName = @{% call openssl.section() %}{{ datarules|join('\n') }}{% endcall %}",
-      "options": {
-        "extension": true
-      }
-    },
-    {
-      "helper": "certutil",
-      "template": "--extSAN {{ datarules|join(',') }}"
-    }
-  ]
-}
diff --git a/install/share/csrgen/rules/syntaxSubject.json b/install/share/csrgen/rules/syntaxSubject.json
deleted file mode 100644
index af6ec03..0000000
--- a/install/share/csrgen/rules/syntaxSubject.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-  "rules": [
-    {
-      "helper": "openssl",
-      "template": "distinguished_name = {% call openssl.section() %}{{ datarules|reverse|join('\n') }}{% endcall %}"
-    },
-    {
-      "helper": "certutil",
-      "template": "-s {{ datarules|join(',') }}"
-    }
-  ],
-  "options": {
-    "required": true,
-    "data_source_combinator": "and"
-  }
-}
diff --git a/install/share/csrgen/templates/certutil_base.tmpl b/install/share/csrgen/templates/certutil_base.tmpl
deleted file mode 100644
index a5556fd..0000000
--- a/install/share/csrgen/templates/certutil_base.tmpl
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash -e
-
-if [[ $# -lt 1 ]]; then
-echo "Usage: $0 <outfile> [<any> <certutil> <args>]"
-echo "Called as: $0 $@"
-exit 1
-fi
-
-CSR="$1"
-shift
-certutil -R -a -z <(head -c 4096 /dev/urandom) -o "$CSR" {{ options|join(' ') }} "$@"
diff --git a/install/share/csrgen/templates/openssl_base.tmpl b/install/share/csrgen/templates/openssl_base.tmpl
deleted file mode 100644
index 22b1686..0000000
--- a/install/share/csrgen/templates/openssl_base.tmpl
+++ /dev/null
@@ -1,35 +0,0 @@
-{% raw -%}
-{% import "openssl_macros.tmpl" as openssl -%}
-{%- endraw %}
-#!/bin/bash -e
-
-if [[ $# -lt 2 ]]; then
-echo "Usage: $0 <outfile> <keyfile> <other openssl arguments>"
-echo "Called as: $0 $@"
-exit 1
-fi
-
-CONFIG="$(mktemp)"
-CSR="$1"
-KEYFILE="$2"
-shift; shift
-
-echo \
-{% raw %}{% filter quote %}{% endraw -%}
-[ req ]
-prompt = no
-encrypt_key = no
-
-{{ parameters|join('\n') }}
-{% raw %}{% set rendered_extensions -%}{% endraw %}
-{{ extensions|join('\n') }}
-{% raw -%}
-{%- endset -%}
-{% if rendered_extensions -%}
-req_extensions = {% call openssl.section() %}{{ rendered_extensions }}{% endcall %}
-{% endif %}
-{{ openssl.openssl_sections|join('\n\n') }}
-{% endfilter %}{%- endraw %} > "$CONFIG"
-
-openssl req -new -config "$CONFIG" -out "$CSR" -key "$KEYFILE" "$@"
-rm "$CONFIG"
diff --git a/install/share/csrgen/templates/openssl_macros.tmpl b/install/share/csrgen/templates/openssl_macros.tmpl
deleted file mode 100644
index d31b8fe..0000000
--- a/install/share/csrgen/templates/openssl_macros.tmpl
+++ /dev/null
@@ -1,29 +0,0 @@
-{# List containing rendered sections to be included at end #}
-{% set openssl_sections = [] %}
-
-{#
-List containing one entry for each section name allocated. Because of
-scoping rules, we need to use a list so that it can be a "per-render global"
-that gets updated in place. Real globals are shared by all templates with the
-same environment, and variables defined in the macro don't persist after the
-macro invocation ends.
-#}
-{% set openssl_section_num = [] %}
-
-{% macro section() -%}
-{% set name -%}
-sec{{ openssl_section_num|length -}}
-{% endset -%}
-{% do openssl_section_num.append('') -%}
-{% set contents %}{{ caller() }}{% endset -%}
-{% if contents -%}
-{% set sectiondata = formatsection(name, contents) -%}
-{% do openssl_sections.append(sectiondata) -%}
-{% endif -%}
-{{ name -}}
-{% endmacro %}
-
-{% macro formatsection(name, contents) -%}
-[ {{ name }} ]
-{{ contents -}}
-{% endmacro %}
diff --git a/ipaclient/csrgen.py b/ipaclient/csrgen.py
index 96100ae..8c41e39 100644
--- a/ipaclient/csrgen.py
+++ b/ipaclient/csrgen.py
@@ -8,6 +8,8 @@
 import pipes
 import traceback
 
+import pkg_resources
+
 import jinja2
 import jinja2.ext
 import jinja2.sandbox
@@ -15,7 +17,6 @@
 
 from ipalib import errors
 from ipalib.text import _
-from ipaplatform.paths import paths
 from ipapython.ipa_log_manager import log_mgr
 
 if six.PY3:
@@ -72,10 +73,14 @@ class Formatter(object):
     """
     base_template_name = None
 
-    def __init__(self, csr_data_dir=paths.CSR_DATA_DIR):
+    def __init__(self, csr_data_dir=None):
+        if csr_data_dir is not None:
+            loader = jinja2.FileSystemLoader(
+                os.path.join(csr_data_dir, 'templates'))
+        else:
+            loader = jinja2.PackageLoader('ipaclient', 'csrgen/templates')
         self.jinja2 = jinja2.sandbox.SandboxedEnvironment(
-            loader=jinja2.FileSystemLoader(
-                os.path.join(csr_data_dir, 'templates')),
+            loader=loader,
             extensions=[jinja2.ext.ExprStmtExtension, IPAExtension],
             keep_trailing_newline=True, undefined=IndexableUndefined)
 
@@ -277,9 +282,13 @@ def rules_for_profile(self, profile_id, helper):
 
 
 class FileRuleProvider(RuleProvider):
-    def __init__(self, csr_data_dir=paths.CSR_DATA_DIR):
+    def __init__(self, csr_data_dir=None):
         self.rules = {}
-        self.csr_data_dir = csr_data_dir
+        if csr_data_dir is None:
+            self.csr_data_dir = pkg_resources.resource_filename(
+                'ipaclient', 'csrgen')
+        else:
+            self.csr_data_dir = csr_data_dir
 
     def _rule(self, rule_name, helper):
         if (rule_name, helper) not in self.rules:
diff --git a/ipaclient/csrgen/profiles/caIPAserviceCert.json b/ipaclient/csrgen/profiles/caIPAserviceCert.json
new file mode 100644
index 0000000..114d2ff
--- /dev/null
+++ b/ipaclient/csrgen/profiles/caIPAserviceCert.json
@@ -0,0 +1,15 @@
+[
+    {
+        "syntax": "syntaxSubject",
+        "data": [
+            "dataHostCN",
+            "dataSubjectBase"
+        ]
+    },
+    {
+        "syntax": "syntaxSAN",
+        "data": [
+            "dataDNS"
+        ]
+    }
+]
diff --git a/ipaclient/csrgen/profiles/userCert.json b/ipaclient/csrgen/profiles/userCert.json
new file mode 100644
index 0000000..d6cf5cf
--- /dev/null
+++ b/ipaclient/csrgen/profiles/userCert.json
@@ -0,0 +1,15 @@
+[
+    {
+        "syntax": "syntaxSubject",
+        "data": [
+            "dataUsernameCN",
+            "dataSubjectBase"
+        ]
+    },
+    {
+        "syntax": "syntaxSAN",
+        "data": [
+            "dataEmail"
+        ]
+    }
+]
diff --git a/ipaclient/csrgen/rules/dataDNS.json b/ipaclient/csrgen/rules/dataDNS.json
new file mode 100644
index 0000000..2663f11
--- /dev/null
+++ b/ipaclient/csrgen/rules/dataDNS.json
@@ -0,0 +1,15 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "DNS = {{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}"
+    },
+    {
+      "helper": "certutil",
+      "template": "dns:{{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]|quote}}"
+    }
+  ],
+  "options": {
+    "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]"
+  }
+}
diff --git a/ipaclient/csrgen/rules/dataEmail.json b/ipaclient/csrgen/rules/dataEmail.json
new file mode 100644
index 0000000..2eae9fb
--- /dev/null
+++ b/ipaclient/csrgen/rules/dataEmail.json
@@ -0,0 +1,15 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "email = {{subject.mail.0}}"
+    },
+    {
+      "helper": "certutil",
+      "template": "email:{{subject.mail.0|quote}}"
+    }
+  ],
+  "options": {
+    "data_source": "subject.mail.0"
+  }
+}
diff --git a/ipaclient/csrgen/rules/dataHostCN.json b/ipaclient/csrgen/rules/dataHostCN.json
new file mode 100644
index 0000000..5c415bb
--- /dev/null
+++ b/ipaclient/csrgen/rules/dataHostCN.json
@@ -0,0 +1,15 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]}}"
+    },
+    {
+      "helper": "certutil",
+      "template": "CN={{subject.krbprincipalname.0.partition('/')[2].partition('@')[0]|quote}}"
+    }
+  ],
+  "options": {
+    "data_source": "subject.krbprincipalname.0.partition('/')[2].partition('@')[0]"
+  }
+}
diff --git a/ipaclient/csrgen/rules/dataSubjectBase.json b/ipaclient/csrgen/rules/dataSubjectBase.json
new file mode 100644
index 0000000..309dfb1
--- /dev/null
+++ b/ipaclient/csrgen/rules/dataSubjectBase.json
@@ -0,0 +1,15 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "{{config.ipacertificatesubjectbase.0}}"
+    },
+    {
+      "helper": "certutil",
+      "template": "{{config.ipacertificatesubjectbase.0|quote}}"
+    }
+  ],
+  "options": {
+    "data_source": "config.ipacertificatesubjectbase.0"
+  }
+}
diff --git a/ipaclient/csrgen/rules/dataUsernameCN.json b/ipaclient/csrgen/rules/dataUsernameCN.json
new file mode 100644
index 0000000..37e7e01
--- /dev/null
+++ b/ipaclient/csrgen/rules/dataUsernameCN.json
@@ -0,0 +1,15 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "CN={{subject.uid.0}}"
+    },
+    {
+      "helper": "certutil",
+      "template": "CN={{subject.uid.0|quote}}"
+    }
+  ],
+  "options": {
+    "data_source": "subject.uid.0"
+  }
+}
diff --git a/ipaclient/csrgen/rules/syntaxSAN.json b/ipaclient/csrgen/rules/syntaxSAN.json
new file mode 100644
index 0000000..122eb12
--- /dev/null
+++ b/ipaclient/csrgen/rules/syntaxSAN.json
@@ -0,0 +1,15 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "subjectAltName = @{% call openssl.section() %}{{ datarules|join('\n') }}{% endcall %}",
+      "options": {
+        "extension": true
+      }
+    },
+    {
+      "helper": "certutil",
+      "template": "--extSAN {{ datarules|join(',') }}"
+    }
+  ]
+}
diff --git a/ipaclient/csrgen/rules/syntaxSubject.json b/ipaclient/csrgen/rules/syntaxSubject.json
new file mode 100644
index 0000000..af6ec03
--- /dev/null
+++ b/ipaclient/csrgen/rules/syntaxSubject.json
@@ -0,0 +1,16 @@
+{
+  "rules": [
+    {
+      "helper": "openssl",
+      "template": "distinguished_name = {% call openssl.section() %}{{ datarules|reverse|join('\n') }}{% endcall %}"
+    },
+    {
+      "helper": "certutil",
+      "template": "-s {{ datarules|join(',') }}"
+    }
+  ],
+  "options": {
+    "required": true,
+    "data_source_combinator": "and"
+  }
+}
diff --git a/ipaclient/csrgen/templates/certutil_base.tmpl b/ipaclient/csrgen/templates/certutil_base.tmpl
new file mode 100644
index 0000000..a5556fd
--- /dev/null
+++ b/ipaclient/csrgen/templates/certutil_base.tmpl
@@ -0,0 +1,11 @@
+#!/bin/bash -e
+
+if [[ $# -lt 1 ]]; then
+echo "Usage: $0 <outfile> [<any> <certutil> <args>]"
+echo "Called as: $0 $@"
+exit 1
+fi
+
+CSR="$1"
+shift
+certutil -R -a -z <(head -c 4096 /dev/urandom) -o "$CSR" {{ options|join(' ') }} "$@"
diff --git a/ipaclient/csrgen/templates/openssl_base.tmpl b/ipaclient/csrgen/templates/openssl_base.tmpl
new file mode 100644
index 0000000..22b1686
--- /dev/null
+++ b/ipaclient/csrgen/templates/openssl_base.tmpl
@@ -0,0 +1,35 @@
+{% raw -%}
+{% import "openssl_macros.tmpl" as openssl -%}
+{%- endraw %}
+#!/bin/bash -e
+
+if [[ $# -lt 2 ]]; then
+echo "Usage: $0 <outfile> <keyfile> <other openssl arguments>"
+echo "Called as: $0 $@"
+exit 1
+fi
+
+CONFIG="$(mktemp)"
+CSR="$1"
+KEYFILE="$2"
+shift; shift
+
+echo \
+{% raw %}{% filter quote %}{% endraw -%}
+[ req ]
+prompt = no
+encrypt_key = no
+
+{{ parameters|join('\n') }}
+{% raw %}{% set rendered_extensions -%}{% endraw %}
+{{ extensions|join('\n') }}
+{% raw -%}
+{%- endset -%}
+{% if rendered_extensions -%}
+req_extensions = {% call openssl.section() %}{{ rendered_extensions }}{% endcall %}
+{% endif %}
+{{ openssl.openssl_sections|join('\n\n') }}
+{% endfilter %}{%- endraw %} > "$CONFIG"
+
+openssl req -new -config "$CONFIG" -out "$CSR" -key "$KEYFILE" "$@"
+rm "$CONFIG"
diff --git a/ipaclient/csrgen/templates/openssl_macros.tmpl b/ipaclient/csrgen/templates/openssl_macros.tmpl
new file mode 100644
index 0000000..d31b8fe
--- /dev/null
+++ b/ipaclient/csrgen/templates/openssl_macros.tmpl
@@ -0,0 +1,29 @@
+{# List containing rendered sections to be included at end #}
+{% set openssl_sections = [] %}
+
+{#
+List containing one entry for each section name allocated. Because of
+scoping rules, we need to use a list so that it can be a "per-render global"
+that gets updated in place. Real globals are shared by all templates with the
+same environment, and variables defined in the macro don't persist after the
+macro invocation ends.
+#}
+{% set openssl_section_num = [] %}
+
+{% macro section() -%}
+{% set name -%}
+sec{{ openssl_section_num|length -}}
+{% endset -%}
+{% do openssl_section_num.append('') -%}
+{% set contents %}{{ caller() }}{% endset -%}
+{% if contents -%}
+{% set sectiondata = formatsection(name, contents) -%}
+{% do openssl_sections.append(sectiondata) -%}
+{% endif -%}
+{{ name -}}
+{% endmacro %}
+
+{% macro formatsection(name, contents) -%}
+[ {{ name }} ]
+{{ contents -}}
+{% endmacro %}
diff --git a/ipaclient/setup.py b/ipaclient/setup.py
index 93cb1e8..f5be7ea 100644
--- a/ipaclient/setup.py
+++ b/ipaclient/setup.py
@@ -43,6 +43,13 @@
             "ipaclient.remote_plugins.2_156",
             "ipaclient.remote_plugins.2_164",
         ],
+        package_data={
+            'ipaclient': [
+                'csrgen/profiles/*.json',
+                'csrgen/rules/*.json',
+                'csrgen/templates/*.tmpl',
+            ],
+        },
         install_requires=[
             "cryptography",
             "ipalib",
@@ -56,5 +63,6 @@
         extras_require={
             "install": ["ipaplatform"],
             "otptoken_yubikey": ["yubico", "usb"]
-        }
+        },
+        zip_safe=False,
     )
diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py
index e4d4f2e..de4ea23 100644
--- a/ipaplatform/base/paths.py
+++ b/ipaplatform/base/paths.py
@@ -238,7 +238,6 @@ class BasePathNamespace(object):
     SCHEMA_COMPAT_ULDIF = "/usr/share/ipa/schema_compat.uldif"
     IPA_JS_PLUGINS_DIR = "/usr/share/ipa/ui/js/plugins"
     UPDATES_DIR = "/usr/share/ipa/updates/"
-    CSR_DATA_DIR = "/usr/share/ipa/csrgen"
     DICT_WORDS = "/usr/share/dict/words"
     CACHE_IPA_SESSIONS = "/var/cache/ipa/sessions"
     VAR_KERBEROS_KRB5KDC_DIR = "/var/kerberos/krb5kdc/"

From dd84c61d4ff8ec842255d9fa8ce83cdb1fd99425 Mon Sep 17 00:00:00 2001
From: Christian Heimes <chei...@redhat.com>
Date: Fri, 3 Mar 2017 08:35:45 +0100
Subject: [PATCH 2/2] Chain CSR generator file loaders

First try custom location, then csrgen subdir in confdir and finally
fall back to package data.

Signed-off-by: Christian Heimes <chei...@redhat.com>
---
 ipaclient/csrgen.py | 61 ++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 44 insertions(+), 17 deletions(-)

diff --git a/ipaclient/csrgen.py b/ipaclient/csrgen.py
index 8c41e39..8fb0b32 100644
--- a/ipaclient/csrgen.py
+++ b/ipaclient/csrgen.py
@@ -3,6 +3,7 @@
 #
 
 import collections
+import errno
 import json
 import os.path
 import pipes
@@ -15,6 +16,7 @@
 import jinja2.sandbox
 import six
 
+from ipalib import api
 from ipalib import errors
 from ipalib.text import _
 from ipapython.ipa_log_manager import log_mgr
@@ -74,13 +76,22 @@ class Formatter(object):
     base_template_name = None
 
     def __init__(self, csr_data_dir=None):
+        # chain loaders:
+        # 1) csr_data_dir/templates
+        # 2) /etc/ipa/csrgen/templates
+        # 3) ipaclient/csrgen/templates
+        loaders = []
         if csr_data_dir is not None:
-            loader = jinja2.FileSystemLoader(
+            loaders.append(jinja2.FileSystemLoader(
                 os.path.join(csr_data_dir, 'templates'))
-        else:
-            loader = jinja2.PackageLoader('ipaclient', 'csrgen/templates')
+            )
+        loaders.append(jinja2.FileSystemLoader(
+            os.path.join(api.env.confdir, 'csrgen/templates'))
+        )
+        loaders.append(jinja2.PackageLoader('ipaclient', 'csrgen/templates'))
+
         self.jinja2 = jinja2.sandbox.SandboxedEnvironment(
-            loader=loader,
+            loader=jinja2.ChoiceLoader(loaders),
             extensions=[jinja2.ext.ExprStmtExtension, IPAExtension],
             keep_trailing_newline=True, undefined=IndexableUndefined)
 
@@ -284,19 +295,37 @@ def rules_for_profile(self, profile_id, helper):
 class FileRuleProvider(RuleProvider):
     def __init__(self, csr_data_dir=None):
         self.rules = {}
-        if csr_data_dir is None:
-            self.csr_data_dir = pkg_resources.resource_filename(
-                'ipaclient', 'csrgen')
-        else:
-            self.csr_data_dir = csr_data_dir
+        self._csrgen_data_dirs = []
+        if csr_data_dir is not None:
+            self._csrgen_data_dirs.append(csr_data_dir)
+        self._csrgen_data_dirs.append(
+            os.path.join(api.env.confdir, 'csrgen')
+        )
+        self._csrgen_data_dirs.append(
+            pkg_resources.resource_filename('ipaclient', 'csrgen')
+        )
+
+    def _open(self, subdir, filename):
+        for data_dir in self._csrgen_data_dirs:
+            path = os.path.join(data_dir, subdir, filename)
+            try:
+                return open(path)
+            except IOError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+        raise IOError(
+            errno.ENOENT,
+            "'{}' not found in {}".format(
+                os.path.join(subdir, filename),
+                ", ".join(self._csrgen_data_dirs)
+            )
+        )
 
     def _rule(self, rule_name, helper):
         if (rule_name, helper) not in self.rules:
-            rule_path = os.path.join(self.csr_data_dir, 'rules',
-                                     '%s.json' % rule_name)
             try:
-                with open(rule_path) as rule_file:
-                    ruleset = json.load(rule_file)
+                with self._open('rules', '%s.json' % rule_name) as f:
+                    ruleset = json.load(f)
             except IOError:
                 raise errors.NotFound(
                     reason=_('Ruleset %(ruleset)s does not exist.') %
@@ -326,11 +355,9 @@ def _rule(self, rule_name, helper):
         return self.rules[(rule_name, helper)]
 
     def rules_for_profile(self, profile_id, helper):
-        profile_path = os.path.join(self.csr_data_dir, 'profiles',
-                                    '%s.json' % profile_id)
         try:
-            with open(profile_path) as profile_file:
-                profile = json.load(profile_file)
+            with self._open('profiles', '%s.json' % profile_id) as f:
+                profile = json.load(f)
         except IOError:
             raise errors.NotFound(
                 reason=_('No CSR generation rules are defined for profile'
-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to