summaryrefslogtreecommitdiffstats
path: root/roles/lib_openshift/src/class/oc_adm_csr.py
blob: ea11c6ca98ce3e4c50600f0c40b6e4974ab7545e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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 <approve|deny> 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}