From ca0dc1c5898189cf42a6ef2b4b68b37c4e9648a9 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Tue, 15 Aug 2017 18:54:18 -0400 Subject: First attempt at creating the cert signer. --- roles/lib_openshift/src/ansible/oc_adm_csr.py | 36 ++++ roles/lib_openshift/src/class/oc_adm_csr.py | 197 +++++++++++++++++++++ roles/lib_openshift/src/doc/csr | 80 +++++++++ roles/lib_openshift/src/sources.yml | 10 ++ .../src/test/integration/oc_adm_csr.yml | 28 +++ 5 files changed, 351 insertions(+) create mode 100644 roles/lib_openshift/src/ansible/oc_adm_csr.py create mode 100644 roles/lib_openshift/src/class/oc_adm_csr.py create mode 100644 roles/lib_openshift/src/doc/csr create mode 100755 roles/lib_openshift/src/test/integration/oc_adm_csr.yml (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/ansible/oc_adm_csr.py b/roles/lib_openshift/src/ansible/oc_adm_csr.py new file mode 100644 index 000000000..9e43a810b --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_adm_csr.py @@ -0,0 +1,36 @@ +# pylint: skip-file +# flake8: noqa + +def main(): + ''' + ansible oc module for approving certificate signing requests + ''' + + module = AnsibleModule( + argument_spec=dict( + kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'), + state=dict(default='approve', type='str', + choices=['approve', 'deny', 'list']), + debug=dict(default=False, type='bool'), + nodes=dict(default=None, type='list'), + timeout=dict(default=30, type='int'), + approve_all=dict(default=False, type='bool'), + service_account=dict(default='node-bootstrapper', type='str'), + ), + supports_check_mode=True, + mutually_exclusive=[['approve_all', 'nodes']], + ) + + if module.params['nodes'] == []: + module.fail_json(**dict(failed=True, msg='Please specify hosts.')) + + rval = OCcsr.run_ansible(module.params, module.check_mode) + + if 'failed' in rval: + return module.fail_json(**rval) + + return module.exit_json(**rval) + + +if __name__ == '__main__': + main() diff --git a/roles/lib_openshift/src/class/oc_adm_csr.py b/roles/lib_openshift/src/class/oc_adm_csr.py new file mode 100644 index 000000000..ea11c6ca9 --- /dev/null +++ b/roles/lib_openshift/src/class/oc_adm_csr.py @@ -0,0 +1,197 @@ +# pylint: skip-file +# flake8: noqa + + +class OCcsr(OpenShiftCLI): + ''' Class to wrap the oc adm certificate command line''' + kind = 'csr' + + # pylint: disable=too-many-arguments + def __init__(self, + nodes=None, + approve_all=False, + service_account=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for oc adm certificate ''' + super(OCcsr, self).__init__(None, kubeconfig, verbose) + self.service_account = service_account + self.nodes = self.create_nodes(nodes) + self._csrs = [] + self.approve_all = approve_all + self.verbose = verbose + + @property + def csrs(self): + '''property for managing csrs''' + # any processing needed?? + self._csrs = self._get(resource=self.kind)['results'][0]['items'] + return self._csrs + + def create_nodes(self, nodes): + '''create a node object to track csr signing status''' + nodes_list = [] + + if nodes is None: + return nodes_list + + results = self._get(resource='nodes')['results'][0]['items'] + + for node in nodes: + nodes_list.append(dict(name=node, csrs={}, accepted=False, denied=False)) + + for ocnode in results: + if node in ocnode['metadata']['name']: + nodes_list[-1]['accepted'] = True + + return nodes_list + + def get(self): + '''get the current certificate signing requests''' + return self.csrs + + @staticmethod + def action_needed(csr, action): + '''check to see if csr is in desired state''' + if csr['status'] == {}: + return True + + state = csr['status']['conditions'][0]['type'] + + if action == 'approve' and state != 'Approved': + return True + + elif action == 'deny' and state != 'Denied': + return True + + return False + + def match_node(self, csr): + '''match an inc csr to a node in self.nodes''' + for node in self.nodes: + # we have a match + if node['name'] in csr['metadata']['name']: + node['csrs'][csr['metadata']['name']] = csr + + # check that the username is the node and type is 'Approved' + if node['name'] in csr['spec']['username'] and csr['status']: + if csr['status']['conditions'][0]['type'] == 'Approved': + node['accepted'] = True + # check type is 'Denied' and mark node as such + if csr['status'] and csr['status']['conditions'][0]['type'] == 'Denied': + node['denied'] = True + + return node + + return None + + def finished(self): + '''determine if there are more csrs to sign''' + # if nodes is set and we have nodes then return if all nodes are 'accepted' + if self.nodes is not None and len(self.nodes) > 0: + return all([node['accepted'] or node['denied'] for node in self.nodes]) + + # we are approving everything or we still have nodes outstanding + return False + + def manage(self, action): + '''run openshift oc adm ca create-server-cert cmd and store results into self.nodes + + we attempt to verify if the node is one that was given to us to accept. + + action - (allow | deny) + ''' + + results = [] + # There are 2 types of requests: + # - node-bootstrapper-client-ip-172-31-51-246-ec2-internal + # The client request allows the client to talk to the api/controller + # - node-bootstrapper-server-ip-172-31-51-246-ec2-internal + # The server request allows the server to join the cluster + # Here we need to determine how to approve/deny + # we should query the csrs and verify they are from the nodes we thought + for csr in self.csrs: + node = self.match_node(csr) + # oc adm certificate csr + # there are 3 known states: Denied, Aprroved, {} + # verify something is needed by OCcsr.action_needed + # if approve_all, then do it + # if you passed in nodes, you must have a node that matches + if self.approve_all or (node and OCcsr.action_needed(csr, action)): + result = self.openshift_cmd(['certificate', action, csr['metadata']['name']], oadm=True) + # client should have service account name in username field + # server should have node name in username field + if node and csr['metadata']['name'] not in node['csrs']: + node['csrs'][csr['metadata']['name']] = csr + + # accept node in cluster + if node['name'] in csr['spec']['username']: + node['accepted'] = True + + results.append(result) + + return results + + @staticmethod + def run_ansible(params, check_mode=False): + '''run the idempotent ansible code''' + + client = OCcsr(params['nodes'], + params['approve_all'], + params['service_account'], + params['kubeconfig'], + params['debug']) + + state = params['state'] + + api_rval = client.get() + + if state == 'list': + return {'changed': False, 'results': api_rval, 'state': state} + + if state in ['approve', 'deny']: + if check_mode: + return {'changed': True, + 'msg': "CHECK_MODE: Would have {} the certificate.".format(params['state']), + 'state': state} + + all_results = [] + finished = False + timeout = False + import time + # loop for timeout or block until all nodes pass + ctr = 0 + while True: + + all_results.extend(client.manage(params['state'])) + if client.finished(): + finished = True + break + + if params['timeout'] == 0: + if not params['approve_all']: + ctr = 0 + + if ctr * 2 > params['timeout']: + timeout = True + break + + # This provides time for the nodes to send their csr requests between approvals + time.sleep(2) + + ctr += 1 + + for result in all_results: + if result['returncode'] != 0: + return {'failed': True, 'msg': all_results} + + return dict(changed=len(all_results) > 0, + results=all_results, + nodes=client.nodes, + state=state, + finished=finished, + timeout=timeout) + + return {'failed': True, + 'msg': 'Unknown state passed. %s' % state} + diff --git a/roles/lib_openshift/src/doc/csr b/roles/lib_openshift/src/doc/csr new file mode 100644 index 000000000..db72dbda3 --- /dev/null +++ b/roles/lib_openshift/src/doc/csr @@ -0,0 +1,80 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_adm_csr +short_description: Module to approve or deny openshift certificate signing requests +description: + - Wrapper around the openshift `oc adm certificate approve|deny ` command. +options: + state: + description: + - approve|deny|list Approve, deny, and list are the only supported states for certificates + required: false + default: present + choices: + - present + aliases: [] + kubeconfig: + description: + - The path for the kubeconfig file to use for authentication + required: false + default: /etc/origin/master/admin.kubeconfig + aliases: [] + debug: + description: + - Turn on debug output. + required: false + default: False + aliases: [] + nodes: + description: + - A list of the names of the nodes in which to accept the certificates + required: false + default: None + aliases: [] + timeout: + description: + - This flag allows for a timeout value when approving nodes. + required: false + default: 30 + aliases: [] + timeout: + description: + - This flag allows for a timeout value when doing node approvals. + - A zero value for the timeout will block until the nodes have been accepted + required: false + default: 30 + aliases: [] + approve_all: + description: + - This flag allows for the module to approve all CSRs that are found. + - This facilitates testing. + required: false + default: False + aliases: [] + service_account: + description: + - This parameter tells the approval process which service account is being used for the requests + required: false + default: node-bootstrapper + aliases: [] +author: +- "Kenny Woodson " +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: Approve certificates for node xyz + oc_adm_scr: + nodes: + - xyz + timeout: 300 + +- name: Approve certificates for node xyz + oc_adm_scr: + nodes: + - xyz + timeout: 0 +''' diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index e9b6bf261..4636f6b9b 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -9,6 +9,16 @@ oc_adm_ca_server_cert.py: - class/oc_adm_ca_server_cert.py - ansible/oc_adm_ca_server_cert.py +oc_adm_csr.py: +- doc/generated +- doc/license +- lib/import.py +- doc/csr +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- class/oc_adm_csr.py +- ansible/oc_adm_csr.py + oc_adm_manage_node.py: - doc/generated - doc/license diff --git a/roles/lib_openshift/src/test/integration/oc_adm_csr.yml b/roles/lib_openshift/src/test/integration/oc_adm_csr.yml new file mode 100755 index 000000000..cad8e36f5 --- /dev/null +++ b/roles/lib_openshift/src/test/integration/oc_adm_csr.yml @@ -0,0 +1,28 @@ +#!/usr/bin/ansible-playbook --module-path=../../../library/ +# ./oc_adm_csr.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER +--- +- hosts: masters + gather_facts: no + user: root + tasks: + - name: list csrs + oc_adm_csr: + state: list + register: csrout + + - debug: var=csrout + + - name: list csrs + oc_adm_csr: + state: approve + nodes: + - ip-172-31-51-0-ec2-internal + - ip-172-31-51-246-ec2-internal + - ip-172-31-54-12-ec2-internal + - ip-172-31-58-173-ec2-internal + - ip-172-31-58-212-ec2-internal + - ip-172-31-51-246-ec2-internal + - ip-172-31-54-12-ec2-internal + + register: csrout + - debug: var=csrout -- cgit v1.2.1