]> granicus.if.org Git - icinga2/blob - tools/icinga2-discover-agent.cmake
7ff76fd4ef9c05b52264ad5ec3e1e46b90eb2eb9
[icinga2] / tools / icinga2-discover-agent.cmake
1 #!/usr/bin/env python
2 # Copyright (c) 2014 Yusuke Shinyama
3
4 # Permission is hereby granted, free of charge, to any person obtaining a copy
5 # of this software and associated documentation files (the "Software"), to deal
6 # in the Software without restriction, including without limitation the rights
7 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice shall be included in
12 # all copies or substantial portions of the Software.
13 #
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20 # IN THE SOFTWARE.
21
22 from __future__ import print_function
23
24 ##  NetstringParser
25 ##
26 class NetstringParser(object):
27     """
28     Decodes a netstring to a list of Python strings.
29
30     >>> parser = NetstringParser()
31     >>> parser.feed('3:456,')
32     >>> parser.results
33     ['456']
34     >>> NetstringParser.parse('3:abc,4:defg,')
35     ['abc', 'defg']
36     """
37     
38     def __init__(self):
39         self.results = []
40         self.reset()
41         return
42
43     def reset(self):
44         self._data = ''
45         self._length = 0
46         self._parse = self._parse_len
47         return
48         
49     def feed(self, s):
50         i = 0
51         while i < len(s):
52             i = self._parse(s, i)
53         return
54         
55     def _parse_len(self, s, i):
56         while i < len(s):
57             c = s[i]
58             if c < '0' or '9' < c:
59                 self._parse = self._parse_sep
60                 break
61             self._length *= 10
62             self._length += ord(c)-48
63             i += 1
64         return i
65         
66     def _parse_sep(self, s, i):
67         if s[i] != ':': raise SyntaxError(i)
68         self._parse = self._parse_data
69         return i+1
70         
71     def _parse_data(self, s, i):
72         n = min(self._length, len(s)-i)
73         self._data += s[i:i+n]
74         self._length -= n
75         if self._length == 0:
76             self._parse = self._parse_end
77         return i+n
78         
79     def _parse_end(self, s, i):
80         if s[i] != ',': raise SyntaxError(i)
81         self.add_data(self._data)
82         self.reset()
83         return i+1
84
85     def add_data(self, data):
86         self.results.append(data)
87         return
88
89     @classmethod
90     def parse(klass, s):
91         self = klass()
92         self.feed(s)
93         return self.results
94
95 # Icinga 2
96 # Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)
97 #
98 # This program is free software; you can redistribute it and/or
99 # modify it under the terms of the GNU General Public License
100 # as published by the Free Software Foundation; either version 2
101 # of the License, or (at your option) any later version.
102 #
103 # This program is distributed in the hope that it will be useful,
104 # but WITHOUT ANY WARRANTY; without even the implied warranty of
105 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
106 # GNU General Public License for more details.
107 #
108 # You should have received a copy of the GNU General Public License
109 # along with this program; if not, write to the Free Software Foundation
110 # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
111
112 import socket, ssl, sys, json, os, hashlib, time
113
114 def warning(*objs):
115     print(*objs, file=sys.stderr)
116
117 if len(sys.argv) < 2:
118     warning("Syntax: %s <host> [<port>]" % (sys.argv[0]))
119     sys.exit(1)
120
121 host = sys.argv[1]
122 if len(sys.argv) > 2:
123     port = int(sys.argv[2])
124 else:
125     port = 8483
126
127 agentpki = "@CMAKE_INSTALL_FULL_SYSCONFDIR@/icinga2/pki/agent"
128 keyfile = agentpki + "/agent.key"
129 certfile = agentpki + "/agent.crt"
130 cafile = agentpki + "/ca.crt"
131
132 if not os.path.isfile(certfile):
133     warning("Certificate file (" + certfile + ") not found.")
134     warning("Make sure the agent certificates are set up properly.")
135     sys.exit(1)
136
137 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
138
139 # require a certificate from the server
140 ssl_sock = ssl.wrap_socket(s,
141                            keyfile=keyfile,
142                            certfile=certfile,
143                            ca_certs=cafile,
144                            cert_reqs=ssl.CERT_REQUIRED)
145
146 ssl_sock.connect((host, port))
147
148 cn = None
149
150 subject = ssl_sock.getpeercert()["subject"]
151
152 for prdn in subject:
153     rdn = prdn[0]
154     if rdn[0] == "commonName":
155         cn = rdn[1]
156
157 if cn == None:
158     warning("Agent certificate does not have a commonName:", repr(subject))
159     sys.exit(1)
160
161 ssl_sock.write('20:{"method":"get_crs"},')
162
163 nsp = NetstringParser()
164 while True:
165     data = ssl_sock.read()
166     if not data:
167         break
168     nsp.feed(data)
169
170 ssl_sock.close()
171
172 if len(nsp.results) != 1:
173     warning("Agent returned invalid response: ", repr(nsp.results))
174     sys.exit(1)
175
176 response = json.loads(nsp.results[0])
177 method = response['method']
178
179 if method != "push_crs":
180     warning("Agent did not return any check results. Make sure you're using the master certificate.")
181     sys.exit(1)
182
183 params = response["params"]
184 params["seen"] = time.time()
185
186 inventory_file = "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/icinga2/agent/inventory/" + hashlib.sha256(cn).hexdigest()
187 fp = open(inventory_file, "w")
188 inventory_info = { "identity": cn, "params": params }
189 json.dump(inventory_info, fp)
190 fp.close()
191
192 peer_file = "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/icinga2/agent/inventory/" + hashlib.sha256(cn).hexdigest() + ".peer"
193 fp = open(peer_file, "w")
194 peer_info = { "agent_host": host, "agent_port": port }
195 json.dump(peer_info, fp)
196 fp.close()
197
198 print("Inventory information has been updated for agent '%s'." % (cn))
199 sys.exit(0)