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
198
199
200
201
202
203
204
205
206
207
208
209
|
# 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 get_csr_request(self, request):
'''base64 decode the request object and call openssl to determine the
subject and specifically the CN: from the request
Output:
(0, '...
Subject: O=system:nodes, CN=system:node:ip-172-31-54-54.ec2.internal
...')
'''
import base64
return self._run(['openssl', 'req', '-noout', '-text'], base64.b64decode(request))[1]
def match_node(self, csr):
'''match an inc csr to a node in self.nodes'''
for node in self.nodes:
# we need to match based upon the csr's request certificate's CN
if node['name'] in self.get_csr_request(csr['spec']['request']):
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}
|