From 997d408f718d651a3e679cd2c17fd5ba02e4043a Mon Sep 17 00:00:00 2001
From: Leonardo Rossetti <lrossett@redhat.com>
Date: Sep 24 2021 13:35:35 +0000
Subject: cert generation re-factor


---

diff --git a/operator/molecule/default/tasks/kojihub_test.yml b/operator/molecule/default/tasks/kojihub_test.yml
index 625d636..a546147 100644
--- a/operator/molecule/default/tasks/kojihub_test.yml
+++ b/operator/molecule/default/tasks/kojihub_test.yml
@@ -70,7 +70,6 @@
   - assert:
       that:
         - kojihub_secrets.resources|length == 1
-        - kojihub_secrets.resources[0].metadata.labels['app'] == 'koji-hub'
         - "'client.pem' in kojihub_secrets.resources[0].data"
 
 - block:
diff --git a/operator/requirements.yml b/operator/requirements.yml
index de1ec48..06fe6d2 100644
--- a/operator/requirements.yml
+++ b/operator/requirements.yml
@@ -1,6 +1,6 @@
 ---
 collections:
-  - name: community.kubernetes
+  - name: kubernetes.core
     version: "1.2.1"
   - name: operator_sdk.util
     version: "0.2.0"
diff --git a/operator/roles/koji-builder/tasks/builders/repo.yml b/operator/roles/koji-builder/tasks/builders/repo.yml
index c93aa24..d6dcd08 100644
--- a/operator/roles/koji-builder/tasks/builders/repo.yml
+++ b/operator/roles/koji-builder/tasks/builders/repo.yml
@@ -1,4 +1,22 @@
 ---
+- name: Retrieve CA secret
+  k8s_info:
+    api_version: v1
+    kind: Secret
+    name: "{{ koji_builder_cacert_secret }}"
+    namespace: "{{ meta.namespace }}"
+  register: k8s_secrets
+
+- name: Fail if CA secret is not found 
+  fail:
+    msg: "Secret {{ koji_builder_cacert_secret }} not found."
+  when: k8s_secrets.resources|length == 0
+
+- name: Copy CA certificate to temporary folder
+  copy:
+    content: "{{ k8s_secrets.resources[0].data.cert | b64decode }}"
+    dest: "{{ koji_dir_path }}/ca.pem"
+
 - block:
     - name: Download admin client certificate
       k8s_info:
@@ -18,7 +36,7 @@
 
 - koji_host:
     server: "{{ koji_builder_koji_hub_url }}/kojihub"
-    host: "{{ koji_builder_host_name }}"
+    host: "{{ meta.name }}"
     archs: "{{ koji_builder_host_archs }}"
     channels: [ "createrepo" ]
     ssl_auth:
diff --git a/operator/roles/koji-builder/tasks/cert.yml b/operator/roles/koji-builder/tasks/cert.yml
index 5da5e05..e009871 100644
--- a/operator/roles/koji-builder/tasks/cert.yml
+++ b/operator/roles/koji-builder/tasks/cert.yml
@@ -1,66 +1,8 @@
-- name: Check for koji-builder client secret existence 
-  k8s_info:
-    api_version: v1
-    kind: Secret
-    name: "{{ koji_builder_client_cert_secret }}"
-    namespace: "{{ meta.namespace }}"
-  register: clientcert_query
-
-- name: Retrieve ca secret
-  block:
-    - k8s_info:
-        api_version: v1
-        kind: Secret
-        name: "{{ koji_builder_cacert_secret }}"
-        namespace: "{{ meta.namespace }}"
-      register: k8s_secrets
-    - fail:
-        msg: "Secret {{ koji_builder_cacert_secret }} not found."
-      when: k8s_secrets.resources|length == 0
-    - set_fact:
-        ca: "{{ k8s_secrets.resources[0] }}"
-    - copy:
-        content: "{{ ca.data.cert | b64decode }}"
-        dest: "{{ koji_dir_path }}/ca_cert.pem"
-    - copy:
-        content: "{{ ca.data.key | b64decode }}"
-        dest: "{{ koji_dir_path }}/ca_key.pem"
-      when: clientcert_query.resources|length == 0
-    - copy:
-        content: "{{ ca.data.cert | b64decode }}"
-        dest: "{{ koji_dir_path }}/ca.pem"
-
-- name: Client certificate creation
-  block:
-    - openssl_privatekey:
-        path: "{{ koji_dir_path }}/client_key.pem"
-        size: 4096
-    - openssl_csr:
-        path: "{{ koji_dir_path }}/client_req.pem"
-        privatekey_path: "{{ koji_dir_path }}/client_key.pem"
-        common_name: "{{ koji_builder_host_name }}"
-    - openssl_certificate:
-        path: "{{ koji_dir_path }}/client_cert.pem"
-        csr_path: "{{ koji_dir_path }}/client_req.pem"
-        ownca_path: "{{ koji_dir_path }}/ca_cert.pem"
-        ownca_privatekey_path: "{{ koji_dir_path }}/ca_key.pem"
-        provider: ownca
-  when: clientcert_query.resources|length == 0
-
-- name: Kubernetes client certificate secret creation
-  block:
-    - k8s:
-        definition:
-          apiVersion: v1
-          kind: Secret
-          metadata:
-            name: "{{ koji_builder_client_cert_secret }}"
-            namespace: "{{ meta.namespace }}"
-            labels:
-              app: koji-builder
-          data:
-            # tls.crt: "{{ lookup('file', koji_dir_path + '/client_cert.pem') | b64encode }}"
-            # tls.key: "{{ lookup('file', koji_dir_path + '/client_key.pem') | b64encode }}"
-            client.pem: "{{ (lookup('file', koji_dir_path + '/client_key.pem') + '\n' + lookup('file', koji_dir_path + '/client_cert.pem')) | b64encode }}"
-  when: clientcert_query.resources|length == 0
-
+- koji_client_cert:
+    k8s_ca: "{{ meta.namespace }}/{{ koji_builder_cacert_secret }}"
+    k8s_cert: "{{ meta.namespace }}/{{ koji_builder_client_cert_secret }}"
+    k8s_meta:
+      labels:
+        app: koji-builder
+    username: "{{ meta.name }}"
+    force: false
diff --git a/operator/roles/koji-builder/tasks/main.yml b/operator/roles/koji-builder/tasks/main.yml
index 87e91a8..f7ee336 100644
--- a/operator/roles/koji-builder/tasks/main.yml
+++ b/operator/roles/koji-builder/tasks/main.yml
@@ -22,7 +22,6 @@
         src: "{{ koji_dir_path }}/configmap.yml"
         wait: true
         namespace: "{{ meta.namespace }}"
- 
 
 - include_tasks: builders/repo.yml
   when: "koji_builder_type == 'repo'"
diff --git a/operator/roles/koji-certs/tasks/main.yml b/operator/roles/koji-certs/tasks/main.yml
new file mode 100644
index 0000000..354d2dd
--- /dev/null
+++ b/operator/roles/koji-certs/tasks/main.yml
@@ -0,0 +1,9 @@
+---
+"""
+koji_ca_k8s: '$name/$namespace'
+koji_cert_dir: ''
+koji_cert_cn: ''
+koji_cert_client_cert: '$name/$namespace'
+koji_cert_state: 'present|absent'
+koji_cert_force: true|false
+"""
diff --git a/operator/roles/koji-hub/meta/main.yml b/operator/roles/koji-hub/meta/main.yml
new file mode 100644
index 0000000..a2634aa
--- /dev/null
+++ b/operator/roles/koji-hub/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+  - role: koji-lib
diff --git a/operator/roles/koji-hub/tasks/cert.yml b/operator/roles/koji-hub/tasks/cert.yml
index 8e0ac06..da11203 100644
--- a/operator/roles/koji-hub/tasks/cert.yml
+++ b/operator/roles/koji-hub/tasks/cert.yml
@@ -10,11 +10,6 @@
     suffix: cert
   register: cert_dir
 
-    # - block:
-    #- set_fact:
-    #   koji_hub_ca_cert_secret: "{{ k8s_mboxes.resources[0].spec.root_ca_secret_name }}"
-    #when: koji_mbox|length > 0
-
 # koji-hub ca cert
 - name: Root CA creation
   k8s_info:
@@ -107,78 +102,28 @@
   when: k8s_servicecert_query.resources|length == 0
 
 # koji-hub admin client cert
-- name: check if admin client cert exists
-  k8s_info:
-    api_version: v1
-    kind: Secret
-    name: "{{ koji_hub_admin_client_cert }}"
-    namespace: "{{ meta.namespace }}"
-  register: k8s_admin_cert_query
-- block:
-    - name: Admin user certificate creation
-      openssl_privatekey:
-        path: "{{ cert_dir.path }}/admin_key.pem"
-        size: 4096
-    - openssl_csr:
-        path: "{{ cert_dir.path }}/admin_req.pem"
-        privatekey_path: "{{ cert_dir.path }}/admin_key.pem"
-        common_name: "{{ koji_hub_admin_username }}"
-    - openssl_certificate:
-        path: "{{ cert_dir.path }}/admin_cert.pem"
-        csr_path: "{{ cert_dir.path }}/admin_req.pem"
-        ownca_path: "{{ cert_dir.path }}/ca_cert.pem"
-        ownca_privatekey_path: "{{ cert_dir.path }}/ca_key.pem"
-        provider: ownca
-    - k8s:
-        definition:
-          apiVersion: v1
-          kind: Secret
-          metadata:
-            name: "{{ koji_hub_admin_client_cert }}"
-            namespace: "{{ meta.namespace }}"
-            labels:
-              app: koji-hub
-          data:
-            client.pem: "{{ (lookup('file', cert_dir.path + '/admin_key.pem') + '\n' + lookup('file', cert_dir.path + '/admin_cert.pem')) | b64encode }}"
-    - copy:
-        content: "{{ lookup('file', cert_dir.path + '/admin_key.pem') + '\n' + lookup('file', cert_dir.path + '/admin_cert.pem') }}"
-        dest: "{{ lookup('env', 'HOME') }}/.koji/certs/{{ meta.name  }}/client.pem"
-  when: k8s_admin_cert_query.resources|length == 0
+- name: Create kojiadmin client cert
+  koji_client_cert:
+    k8s_ca: "{{ meta.namespace }}/{{ koji_hub_ca_cert_secret }}"
+    k8s_cert: "{{ meta.namespace }}/{{ koji_hub_admin_client_cert }}"  
+    username: "{{ koji_hub_admin_username }}"
+    force: false
+    state: present
+  register: koji_hub_admin_cert
+
+- copy:
+    content: "{{ koji_hub_admin_cert.result.pemfile }}"
+    dest: "{{ lookup('env', 'HOME') }}/.koji/certs/{{ meta.name  }}/client.pem"
+  when: koji_hub_admin_cert.changed
 
 # koji-web client cert
-- name: check if kojiweb client cert exists
-  k8s_info:
-    api_version: v1
-    kind: Secret
-    name: "{{ koji_hub_web_client_cert }}"
-    namespace: "{{ meta.namespace }}"
-  register: k8s_web_cert_query
-- block:
-    - openssl_privatekey:
-        path: "{{ cert_dir.path }}/client_web_key.pem"
-        size: 4096
-    - openssl_csr:
-        path: "{{ cert_dir.path }}/client_web_req.pem"
-        privatekey_path: "{{ cert_dir.path }}/client_web_key.pem"
-        common_name: "{{ koji_hub_web_client_username }}"
-    - openssl_certificate:
-        path: "{{ cert_dir.path }}/client_web_cert.pem"
-        csr_path: "{{ cert_dir.path }}/client_web_req.pem"
-        ownca_path: "{{ cert_dir.path }}/ca_cert.pem"
-        ownca_privatekey_path: "{{ cert_dir.path }}/ca_key.pem"
-        provider: ownca
-    - k8s:
-        definition:
-          apiVersion: v1
-          kind: Secret
-          metadata:
-            name: "{{ koji_hub_web_client_cert }}"
-            namespace: "{{ meta.namespace }}"
-            labels:
-              app: koji-hub
-          data:
-            client.pem: "{{ (lookup('file', cert_dir.path + '/client_web_key.pem') + '\n' + lookup('file', cert_dir.path + '/client_web_cert.pem')) | b64encode }}"
-  when: k8s_web_cert_query.resources|length == 0
+- name: Create kojiweb client cert
+  koji_client_cert:
+    k8s_ca: "{{ meta.namespace }}/{{ koji_hub_ca_cert_secret }}"
+    k8s_cert: "{{ meta.namespace }}/{{ koji_hub_web_client_cert }}"
+    username: "{{ koji_hub_web_client_cert }}"
+    force: false
+    state: present
 
 - name: cleanup
   file:
diff --git a/operator/roles/koji-lib/action_plugins/koji_client_cert.py b/operator/roles/koji-lib/action_plugins/koji_client_cert.py
new file mode 100644
index 0000000..e97d74d
--- /dev/null
+++ b/operator/roles/koji-lib/action_plugins/koji_client_cert.py
@@ -0,0 +1,289 @@
+# This file is part of the koji-operator project.
+# Copyright (C) 2020  Red Hat, Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+DOCUMENTATION = '''
+---
+module: koji_client_cert
+
+short_description: Ansible module that creates a client certificate for koji access.
+
+description:
+  - This is an action plugin that uses several ansible modules (openssl + kubernetes)
+  - This module loads the root CA from kubernetes and creates signs a new certificate from it
+  - It uses the username as the certfificate common name
+  - It creates the a kubernetes secret with both the private key and certificate in a single file (client.pem)
+  - It returns both client.pem content and the kubernetes secret resource
+
+options:
+    k8s_ca:
+        description:
+            - the kubernetes ca secret namespace and name ($NAMESPACE/$NAME)
+        required: true
+    k8s_secret:
+        description:
+            - the kubernetes client secret namespace and name ($NAMESPACE/$NAME) to be created
+            - it will skip the module run if a secret already exists and 'force' is set to false
+    k8s_meta:
+        description:
+            - A dicitonary indicating any extra fields to add into the secret "metadata" data;
+            - labels, annotations, etc.
+        required: false
+    username:
+        description:
+            - the common name to be used in the certificate (it needs to be the same username used in koji)
+        required: true
+    force:
+      description:
+        - A boolean flag that will re-create the kubernetes client secret in case one was already created with the same name
+      default: false
+
+author:
+    - Red Hat, Inc. and others
+'''
+
+EXAMPLES = '''
+- koji_client_cert:
+    k8s_ca: koji/koji-hub-ca
+    k8s_cert: "koji/koji-web-client-cert
+    k8s_meta:
+      labels:
+        app: myapp
+      annotations:
+        a: 2
+    username: kojiweb
+    force: false
+'''
+
+
+from pathlib import Path
+from base64 import b64encode, b64decode
+
+import yaml
+from ansible.plugins.action import ActionBase
+
+
+class ActionModule(ActionBase):
+    def create_subdirs(self):
+        """Create directories in the run tmp folder"""
+        tmpdir = self._connection._shell.tmpdir
+        Path(f'{tmpdir}/certs/ca').mkdir(parents=True, exist_ok=True)
+        Path(f'{tmpdir}/certs/client').mkdir(parents=True, exist_ok=True)
+        Path(f'{tmpdir}/k8s').mkdir(parents=True, exist_ok=True)
+
+    def file_write(self, path, data):
+        """Helper function to write a file"""
+        with open(path, 'w+') as f:
+            f.write(str(data))
+
+    def file_read(self, path):
+        """Helper function to read from a file"""
+        with open(path, 'r') as f:
+            return f.read()
+
+    def run_setup(self, task_vars=None):
+        """Runs the setup module"""
+        self._execute_module(
+            'setup',
+            module_args=dict(
+                gather_subset='all',
+                gather_timeout=10
+            ),
+            task_vars=task_vars
+        )
+    def run_k8s_info(self, api_version, kind, ref, task_vars=None):
+        """Runs the k8s_info module"""
+        namespace, name = ref.split('/')
+        
+        res = self._execute_module(
+            'kubernetes.core.k8s',
+            module_args=dict(
+                api_version=api_version,
+                kind=kind,
+                namespace=namespace,
+                name=name),
+            task_vars=task_vars)
+
+        if res.get('failed'):
+            return None
+
+        if len(res.get('result', {}).get('data', {})) == 0:
+            return None
+        
+        return res.get('result')
+
+    def run_private_key(self, basedir, key_size=4096, task_vars=None):
+        """Runs the openssl_privatekey module"""
+        return self._execute_module(
+            'ansible.builtin.openssl_privatekey',
+            module_args=dict(
+                path=f'{basedir}/certs/client/key.pem',
+                size=key_size
+            ),
+            task_vars=task_vars
+        )
+
+    def run_openssl_csr(self, basedir, private_key, common_name, task_vars=None):
+        return self._execute_module(
+            'ansible.builtin.openssl_csr',
+            module_args=dict(
+                path=f'{basedir}/certs/client/request.csr',
+                privatekey_path=private_key,
+                common_name=common_name
+            ),
+            task_vars=task_vars
+        )
+
+    def run_openssl_certificate(self, basedir, task_vars=None):
+        return self._execute_module(
+            'ansible.builtin.openssl_certificate',
+            module_args=dict(
+                path=f'{basedir}/certs/client/cert.pem',
+                csr_path=f'{basedir}/certs/client/request.csr',
+                ownca_path=f'{basedir}/certs/ca/cert.pem',
+                ownca_privatekey_path=f'{basedir}/certs/ca/key.pem',
+                provider='ownca'
+            ),
+            task_vars=task_vars
+        )
+
+    def run_k8s(self, data, ref, task_vars=None):
+        namespace, name = ref.split('/')
+
+        return self._execute_module(
+            'kubernetes.core.k8s',
+            module_args=dict(
+                state='present',
+                definition=data
+            ),
+            task_vars=task_vars
+        )
+
+    def run(self, tmp=None, task_vars=None):
+        super(ActionModule, self).run(tmp, task_vars)
+        
+        output = {
+            'failed': False,
+            'changed': False,
+            'skipped': False,
+            'msg': '',
+            'result': {}
+        }
+
+        # plugin params
+        module_args = self._task.args.copy()
+
+        param_k8s_ca = module_args.get('k8s_ca')
+        param_k8s_cert = module_args.get('k8s_cert')
+        param_username = module_args.get('username')
+        param_force = module_args.get('force', False)
+        param_k8s_meta = module_args.get('k8s_meta', {})
+
+        # run setup module so facts are gathered
+        self.run_setup(task_vars)
+        self.create_subdirs()
+        tmpdir = self._connection._shell.tmpdir
+
+        # retrieves an existing client cert (if any)
+        # skips the run in case a secret is found
+        # it won't skip if "force" is set to True
+        k8s_cert = self.run_k8s_info('v1', 'Secret', param_k8s_cert, task_vars=task_vars)
+        if k8s_cert is not None and not param_force:
+            output['skipped'] = True
+            output['msg'] = f'Secret {param_k8s_cert} already exists'
+            return output
+
+        #  retrieves the root ca secret from kubernetes
+        #  fails if no secret is found
+        # fails if secret does not have the expected format
+        k8s_ca = self.run_k8s_info('v1', 'Secret', param_k8s_ca, task_vars=task_vars)
+        if k8s_ca is None:
+            output['failed'] = True
+            output['msg'] = f'Secret {param_k8s_ca}  not found'
+            return output
+        if k8s_ca.get('data', {}).get('cert', None) is None:
+            output['failed'] = True
+            output['msg'] = f'Secret {param_k8s_ca} cannot be parsed'
+            return output
+
+        # writes ca cert and key to tmp task folder
+        ca_cert = self.file_write(f'{tmpdir}/certs/ca/cert.pem', b64decode(k8s_ca['data']['cert']).decode())
+        ca_key = self.file_write(f'{tmpdir}/certs/ca/key.pem', b64decode(k8s_ca['data']['key']).decode())
+
+        # creates the client private key
+        private_key = self.run_private_key(
+            f'{tmpdir}',
+            task_vars=task_vars
+        )
+        if private_key.get('failed'):
+            output['failed'] = True
+            output['msg'] = private_key['msg']
+            return output
+
+        # create client csr
+        csr = self.run_openssl_csr(
+            f'{tmpdir}',
+            private_key['filename'],
+            param_username,
+            task_vars=task_vars)
+        if csr.get('failed'):
+            output['failed'] = True
+            output['msg'] = csr['msg']
+            return output
+        
+        # create client certificate
+        cert = self.run_openssl_certificate(tmpdir, task_vars=task_vars)
+        if cert.get('failed'):
+            output['failed'] = True
+            output['msg'] = cert['msg']
+            return output
+
+        # create k8s cert secret
+        client_pem = self.file_read(private_key['filename']) + self.file_read(cert['filename'])
+        k8s_cert_namespace, k8s_cert_name = param_k8s_cert.split('/')
+        k8s_secret_res = {
+            'apiVersion': 'v1',
+            'kind': 'Secret',
+            'metadata': {
+                'name': k8s_cert_name,
+                'namespace': k8s_cert_namespace,
+                'labels': {
+                    'app': 'koji'
+                }
+            },
+            'data': {
+                'client.pem': b64encode(client_pem.encode()).decode()
+             }
+        }
+        for k,v in param_k8s_meta.items():
+            k8s_secret_res['metadata'][k] = v
+        
+        k8s_client_secret = self.run_k8s(k8s_secret_res, param_k8s_cert, task_vars=task_vars)
+        
+        # set response dict
+        output['changed'] = True
+        output['msg'] = f'Secret {param_k8s_cert} created'
+        output['result'] = {
+            'pemfile': client_pem,
+            'k8s': k8s_client_secret['result']
+        }
+        
+        return output
+
diff --git a/operator/roles/koji-lib/tests/ansible.cfg b/operator/roles/koji-lib/tests/ansible.cfg
new file mode 100644
index 0000000..8a73491
--- /dev/null
+++ b/operator/roles/koji-lib/tests/ansible.cfg
@@ -0,0 +1,2 @@
+[defaults]
+action_plugins = ~/.ansible/plugins/action:/usr/share/ansible/plugins/action:../action_plugins
diff --git a/operator/roles/koji-lib/tests/main.yaml b/operator/roles/koji-lib/tests/main.yaml
new file mode 100644
index 0000000..77f0a4d
--- /dev/null
+++ b/operator/roles/koji-lib/tests/main.yaml
@@ -0,0 +1,61 @@
+- hosts: localhost
+  connection: local
+  collections:
+    - community.kubernetes
+  gather_facts: false
+  tasks:
+    - name: Create temporary cert directory
+      tempfile:
+        state: directory
+        prefix: kojilib
+        suffix: cert
+      register: cert_dir
+
+    - name: Create CA private key
+      openssl_privatekey:
+        path: "{{ cert_dir.path }}/ca_key.pem"
+        size: 4096
+    
+    - name: Create CA CSR
+      openssl_csr:
+        path: "{{ cert_dir.path }}/ca_req.pem"
+        privatekey_path: "{{ cert_dir.path }}/ca_key.pem"
+        common_name: localhost
+        create_subject_key_identifier: true
+        key_usage:
+          - cRLSign
+          - dataEncipherment
+          - digitalSignature
+          - keyCertSign
+          - keyEncipherment
+          - nonRepudiation
+        basic_constraints:
+          - 'CA:TRUE'
+    
+    - name: Create CA certifiate
+      openssl_certificate:
+        path: "{{ cert_dir.path }}/ca_cert.pem"
+        privatekey_path: "{{ cert_dir.path }}/ca_key.pem"
+        csr_path: "{{ cert_dir.path }}/ca_req.pem"
+        provider: selfsigned
+    
+    - name: Create CA kubernetes secret
+      k8s:
+        definition:
+          apiVersion: v1
+          kind: Secret
+          metadata:
+            name: ca
+            namespace: default
+          data:
+            csr: "{{ lookup('file', cert_dir.path + '/ca_req.pem') | b64encode }}"
+            cert: "{{ lookup('file', cert_dir.path + '/ca_cert.pem') | b64encode }}"
+            key: "{{ lookup('file', cert_dir.path + '/ca_key.pem') | b64encode }}"
+
+    - name: Create a koji client secret
+      koji_client_cert:
+        k8s_ca: default/ca
+        k8s_cert: default/client
+        username: builder
+        force: false
+        state: present
diff --git a/operator/roles/koji-lib/tests/requirements.yml b/operator/roles/koji-lib/tests/requirements.yml
new file mode 100644
index 0000000..7ba24a0
--- /dev/null
+++ b/operator/roles/koji-lib/tests/requirements.yml
@@ -0,0 +1,4 @@
+---
+collections:
+  - name: community.kubernetes
+    version: "1.2.1"
diff --git a/operator/roles/kojira/tasks/cert.yml b/operator/roles/kojira/tasks/cert.yml
index 0717ac6..8c379d9 100644
--- a/operator/roles/kojira/tasks/cert.yml
+++ b/operator/roles/kojira/tasks/cert.yml
@@ -12,73 +12,14 @@
     suffix: koji
   register: koji_dir
 
-- block:
-    - set_fact:
-        kojira_cacert_secret: "{{ k8s_mboxes.resources[0].spec.root_ca_secret_name }}"
-  when: kojira_builder_mbox|length > 0
-
-- k8s_info:
-    api_version: v1
-    kind: Secret
-    name: "{{ kojira_client_cert_secret }}"
-    namespace: "{{ meta.namespace }}"
-  register: clientcert_query
-
-- name: Retrieve ca secret
-  block:
-    - k8s_info:
-        api_version: v1
-        kind: Secret
-        name: "{{ kojira_cacert_secret }}"
-        namespace: "{{ meta.namespace }}"
-      register: k8s_secrets
-    - fail:
-        msg: "Secret {{ kojira_cacert_secret }} not found."
-      when: k8s_secrets.resources|length == 0
-    - set_fact:
-        ca: "{{ k8s_secrets.resources[0] }}"
-    - copy:
-        content: "{{ ca.data.cert | b64decode }}"
-        dest: "{{ cert_dir.path }}/ca_cert.pem"
-    - copy:
-        content: "{{ ca.data.key | b64decode }}"
-        dest: "{{ cert_dir.path }}/ca_key.pem"
-      when: clientcert_query.resources|length == 0
-    - copy:
-        content: "{{ ca.data.cert | b64decode }}"
-        dest: "{{ koji_dir.path }}/ca.pem"
-
-- name: Client certificate creation
-  block:
-    - openssl_privatekey:
-        path: "{{ cert_dir.path }}/client_key.pem"
-        size: 4096
-    - openssl_csr:
-        path: "{{ cert_dir.path }}/client_req.pem"
-        privatekey_path: "{{ cert_dir.path }}/client_key.pem"
-        common_name: "{{ kojira_hub_username }}"
-    - openssl_certificate:
-        path: "{{ cert_dir.path }}/client_cert.pem"
-        csr_path: "{{ cert_dir.path }}/client_req.pem"
-        ownca_path: "{{ cert_dir.path }}/ca_cert.pem"
-        ownca_privatekey_path: "{{ cert_dir.path }}/ca_key.pem"
-        provider: ownca
-  when: clientcert_query.resources|length == 0
-
-- name: Kubernetes client certificate secret creation
-  block:
-    - k8s:
-        definition:
-          apiVersion: v1
-          kind: Secret
-          metadata:
-            name: "{{ kojira_client_cert_secret }}"
-            namespace: "{{ meta.namespace }}"
-            labels:
-              app: kojira
-          data:
-            client.pem: "{{ (lookup('file', cert_dir.path + '/client_key.pem') + '\n' + lookup('file', cert_dir.path + '/client_cert.pem')) | b64encode }}"
-  when: clientcert_query.resources|length == 0
+- koji_client_cert:
+    k8s_ca: "{{ meta.namespace }}/{{ kojira_cacert_secret }}"
+    k8s_cert: "{{ meta.namespace  }}/{{ kojira_client_cert_secret }}"
+    k8s_meta:
+      labels:
+        app: kojira
+    username: "{{ kojira_hub_username }}"
+    force: false
 
 - name: cleanup
   file:
diff --git a/operator/roles/kojira/tasks/main.yml b/operator/roles/kojira/tasks/main.yml
index a64b67d..a1258cf 100644
--- a/operator/roles/kojira/tasks/main.yml
+++ b/operator/roles/kojira/tasks/main.yml
@@ -6,19 +6,6 @@
     suffix: deploy
   register: kojira_dir
 
-- block:
-    - name: retrieve mbox resource
-      k8s_info:
-        api_version: apps.fedoraproject.org/v1alpha1
-        kind: Mbox
-        namespace: "{{ meta.namespace }}"
-        name: "{{ kojira_builder_mbox }}"
-      register: k8s_mboxes
-    - fail:
-        msg: "Failed to find mbox {{ kojira_builder_mbox }} in namespace {{ meta.namespace }}"
-      when: k8s_mboxes.resources|length == 0
-  when: kojira_builder_mbox|length > 0
-
 - include_tasks: cert.yml
 
 - block:
diff --git a/operator/roles/kojiuser/meta/main.yml b/operator/roles/kojiuser/meta/main.yml
index 032b390..a2634aa 100644
--- a/operator/roles/kojiuser/meta/main.yml
+++ b/operator/roles/kojiuser/meta/main.yml
@@ -1,2 +1,2 @@
-#dependencies:
-#  - role: ktdreyer.koji_ansible
+dependencies:
+  - role: koji-lib
diff --git a/operator/roles/kojiuser/tasks/create.yml b/operator/roles/kojiuser/tasks/create.yml
index 84064a4..8c8a6e7 100644
--- a/operator/roles/kojiuser/tasks/create.yml
+++ b/operator/roles/kojiuser/tasks/create.yml
@@ -23,76 +23,23 @@
         state: enabled
 
 - block:
-    - name: create temporary cert directory
+    - name: Create user secret
       k8s_info:
         api_version: buildsys.apps.fedoraproject.org/v1alpha1
         kind: KojiHub
         namespace: "{{ koji_user_hub_namespace }}"
         name: "{{ koji_user_hub_name }}"
       register: k8s_query
-    - fail:
-        msg: "Failed to retrieve koji-hub"
-      when: k8s_query.resources | length == 0
-    - k8s_info:
-        api_version: v1
-        kind: Secret
-        namespace: "{{ koji_user_hub_namespace }}"
-        name: "{{ k8s_query.resources[0]['spec']['ca_cert_secret'] }}"
-      register: k8s_secret
-    - fail:
-        msg: 'Could not fetch ca secret'
-      when: k8s_secret.resources | length == 0
-    - tempfile:
-        state: directory
-        prefix: kojiuser
-        suffix: cert
-      register: cert_dir
-    - copy:
-        content: "{{ k8s_secret.resources[0].data.cert | b64decode }}"
-        dest: "{{ cert_dir.path }}/ca_cert.pem"
-    - copy:
-        content: "{{ k8s_secret.resources[0].data.key | b64decode }}"
-        dest: "{{ cert_dir.path }}/ca_key.pem"
-    - k8s_info:
-        api_version: v1
-        kind: Secret
-        name: "{{ meta.name }}-koji-client-cert"
-        namespace: "{{ meta.namespace }}"
-      register: clientcert_query
-    - openssl_privatekey:
-        path: "{{ cert_dir.path }}/client_key.pem"
-        size: 4096
-      when: clientcert_query.resources|length == 0
-    - openssl_csr:
-        path: "{{ cert_dir.path }}/client_req.pem"
-        privatekey_path: "{{ cert_dir.path }}/client_key.pem"
-        common_name: "{{ meta.name }}"
-      when: clientcert_query.resources|length == 0
-    - openssl_certificate:
-        path: "{{ cert_dir.path }}/client_cert.pem"
-        csr_path: "{{ cert_dir.path }}/client_req.pem"
-        ownca_path: "{{ cert_dir.path }}/ca_cert.pem"
-        ownca_privatekey_path: "{{ cert_dir.path }}/ca_key.pem"
-        provider: ownca
-      when: clientcert_query.resources|length == 0
-    - k8s:
-        definition:
-          apiVersion: v1
-          kind: Secret
-          metadata:
-            name: "{{ meta.name }}-koji-client-cert"
-            namespace: "{{ meta.namespace }}"
-            labels:
-              app: koji
-              koji-hub: "{{ koji_user_hub_name }}"
-            annotations:
-              buildsys.apps.fedoraproject.org/hub: "{{ koji_user_hub_name }}/{{ koji_user_hub_namespace }}"
-          data:
-            client.pem: "{{ (lookup('file', cert_dir.path + '/client_key.pem') + '\n' + lookup('file', cert_dir.path + '/client_cert.pem')) | b64encode }}"
-      when: clientcert_query.resources|length == 0
-    - name: cleanup
-      file:
-        path: "{{ cert_dir.path }}"
-        state: absent
-  when:
-    - koji_user_ssl 
+
+    - koji_client_cert:
+        k8s_ca: "{{ koji_user_hub_namespace }}/{{ k8s_query.resources[0]['spec']['ca_cert_secret'] }}"
+        k8s_cert: "{{ meta.namespace }}/{{ meta.name }}-koji-client-cert"
+        k8s_meta:
+          labels:
+            app: koji
+            koji-hub: "{{ koji_user_hub_name }}"
+          annotations:
+            buildsys.apps.fedoraproject.org/hub: "{{ koji_user_hub_name }}/{{ koji_user_hub_namespace }}"
+        username: "{{ koji_user_hub_name }}"
+        force: false
+  when: koji_user_ssl

