The Assimilation Project  based on Assimilation version 1.1.7.1474836767
assimcli.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # vim: smartindent tabstop=4 shiftwidth=4 expandtab number colorcolumn=100
3 #
4 # This file is part of the Assimilation Project.
5 #
6 # Author: Alan Robertson <alanr@unix.sh>
7 # Copyright (C) 2014 - Assimilation Systems Limited
8 #
9 # Free support is available from the Assimilation Project community - http://assimproj.org
10 # Paid support is available from Assimilation Systems Limited - http://assimilationsystems.com
11 #
12 # The Assimilation software is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # The Assimilation software is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with the Assimilation Project software. If not, see http://www.gnu.org/licenses/
24 #
25 #
26 '''
27 Assimilation Command Line tool.
28 We support the following commands:
29  query - perform one of our canned ClientQuery queries
30 '''
31 
32 import sys, os, getent
33 from py2neo import neo4j
34 from query import ClientQuery
35 from consts import CMAconsts
36 from graphnodes import GraphNode
37 from store import Store
38 from AssimCtypes import QUERYINSTALL_DIR, cryptcurve25519_gen_persistent_keypair, \
39  cryptcurve25519_cache_all_keypairs, CMA_KEY_PREFIX, CMAUSERID, BPINSTALL_DIR, \
40  CMAINITFILE
41 from AssimCclasses import pyCryptFrame, pyCryptCurve25519
42 from cmaconfig import ConfigFile
43 from cmadb import Neo4jCreds, CMAdb
44 #
45 # These imports really are necessary - in spite of what pylint thinks...
46 # pylint: disable=W0611
47 import droneinfo, hbring, monitoring
48 from cmainit import CMAinit
49 from bestpractices import BestPractices
50 
51 commands = {}
52 
53 def RegisterCommand(classtoregister):
54  'Register the given function as being a main command'
55  commands[classtoregister.__name__] = classtoregister()
56  return classtoregister
57 
58 #too many local variables
59 #pylint: disable=R0914
60 @RegisterCommand
61 class query(object):
62  "Class for the 'query' action (sub-command)"
63 
64  def __init__(self):
65  pass
66 
67  @staticmethod
68  def usage():
69  "reports usage for this sub-command"
70  return 'query queryname [query-parameter=value ...]'
71 
72  @staticmethod
73  def load_query_object(store, queryname):
74  'Function to return the query object for a given query name'
75  cypher = ('START q=node:ClientQuery("%s:*") WHERE q.queryname="%s" RETURN q LIMIT 1'
76  % (queryname, queryname))
77  ret = store.load_cypher_node(cypher, ClientQuery)
78  if ret is not None:
79  ret.bind_store(store)
80  return ret
81 
82  # pylint R0911 -- too many return statements
83  # pylint: disable=R0911
84  @staticmethod
85  def execute(store, executor_context, otherargs, flagoptions):
86  'Perform command line query and format output as requested.'
87  language = flagoptions.get('language', 'en')
88  fmtstring = flagoptions.get('format', None)
89 
90  if len(otherargs) < 1:
91  usage()
92  print >> sys.stderr, 'Need to supply a query name.'
93  return 1
94  queryname = otherargs[0]
95  nvpairs = otherargs[1:]
96 
97  request = query.load_query_object(store, queryname)
98 
99  if request is None:
100  print >> sys.stderr, ("No query named '%s'." % queryname)
101  return 1
102 
103  param_names = request.parameter_names()
104 
105  params = {}
106  # Convert name=value strings into a Dict
107  for elem in nvpairs:
108  try:
109  (name, value) = elem.split('=')
110  params[name] = value
111  except (AttributeError,ValueError) as err:
112  if len(param_names) == 0:
113  print >> sys.stderr, ('%s query does not take any parameters' % queryname)
114  return 1
115  elif len(param_names) == 1:
116  # It's reasonable to not require the name if there's only one possibility
117  params[param_names[0]] = elem
118  else:
119  print >> sys.stderr, ('[%s] is not a name=value pair' % nvpairs)
120  return 1
121  request.bind_store(store)
122  if not request.supports_cmdline():
123  print >> sys.stderr, (
124  "Query '%s' does not support command line queries" % queryname)
125  return 1
126  try:
127  iterator = request.cmdline_exec(executor_context, language, fmtstring, **params)
128  for line in iterator:
129  print line
130  except ValueError as err:
131  print >> sys.stderr, ('Invalid query [%s %s]: %s.' % (queryname, str(params), str(err)))
132  # pylint W0212 -- access to a protected member
133  # pylint: disable=W0212
134  #print >> sys.stderr, ('CYPHER IS: %s' % request._query)
135  return 1
136  return 0
137 
138 @RegisterCommand
139 class loadqueries(object):
140  "Class for the 'loadquery' action (sub-command). We reload the query table"
141 
142  def __init__(self):
143  'Default init function'
144  pass
145 
146  @staticmethod
147  def usage():
148  "reports usage for this sub-command"
149  return 'loadqueries [optional-querydirectory]'
150 
151  # pylint R0911 -- too many return statements
152  # pylint: disable=R0911
153  @staticmethod
154  def execute(store, executor_context, otherargs, flagoptions):
155  'Load queries from the specified directory.'
156 
157  executor_context = executor_context
158  flagoptions = flagoptions
159 
160  if len(otherargs) > 1:
161  return usage()
162  elif len(otherargs) == 1:
163  querydir = otherargs[0]
164  else:
165  querydir = QUERYINSTALL_DIR
166 
167  qcount = 0
168  for q in ClientQuery.load_tree(store, querydir):
169  qcount += 1
170  q = q
171  #Store.debug = True
172  store.commit()
173  return 0 if qcount > 0 else 1
174 
175 @RegisterCommand
176 class loadbp(object):
177  "Class for the 'loadbp' action (sub-command). We load up a set of best practices"
178 
179  def __init__(self):
180  'Default init function'
181  pass
182 
183  @staticmethod
184  def usage():
185  "reports usage for this sub-command"
186  return 'loadbp [ruleset-directory ruleset-name [based-on-ruleset-name]]'
187 
188  @staticmethod
189  def execute(store, executor_context, otherargs, flagoptions):
190  'Load all best practice files we find in the specified directory.'
191  executor_context = executor_context
192  flagoptions = flagoptions
193  basedon = None
194 
195  if len(otherargs) not in (0,2,3):
196  return usage()
197  elif len(otherargs) >= 2:
198  bpdir = otherargs[0]
199  rulesetname = otherargs[1]
200  if len(otherargs) > 2:
201  basedon = otherargs[2]
202  else:
203  bpdir = BPINSTALL_DIR
204  rulesetname = CMAconsts.BASERULESETNAME
205 
206  qcount = 0
207  for q in BestPractices.load_directory(store, bpdir, rulesetname, basedon):
208  qcount += 1
209  q = q
210  store.commit()
211  return 0 if qcount > 0 else 1
212 
213 @RegisterCommand
214 class genkeys(object):
215  'Generate two CMA keys and store in optional directory.'
216 
217  def __init__(self):
218  'Default init function'
219  pass
220 
221  @staticmethod
222  def usage():
223  "reports usage for this sub-command"
224  return 'genkeys'
225 
226  @staticmethod
227  def execute(store, _executor_context, otherargs, _flagoptions):
228  'Generate the desired key-pairs'
229  store = store
230 
231  if os.geteuid() != 0 or len(otherargs) > 0:
232  return usage()
234  cmaidlist = pyCryptFrame.get_cma_key_ids()
235  cmaidlist.sort()
236  if len(cmaidlist) == 0:
237  print ('No CMA keys found. Generating two CMA key-pairs to start.')
238  for sequence in (0, 1):
239  print >> sys.stderr, "Generating key id", sequence
240  cryptcurve25519_gen_persistent_keypair('%s%05d' % (CMA_KEY_PREFIX, sequence))
242  cmaidlist = pyCryptFrame.get_cma_key_ids()
243  elif len(cmaidlist) == 1:
244  lastkey = cmaidlist[0]
245  lastseqno = int(lastkey[len(CMA_KEY_PREFIX):])
246  newkeyid = ('%s%05d' % (CMA_KEY_PREFIX, lastseqno + 1))
247  print ('Generating an additional CMA key-pair.')
250  cmaidlist = pyCryptFrame.get_cma_key_ids()
251  if len(cmaidlist) != 2:
252  print ('Unexpected number of CMA keys. Expecting 2, but got %d.'
253  % len(cmaidlist))
254  extras = []
255  privatecount = 0
256  userinfo = getent.passwd(CMAUSERID)
257  if userinfo is None:
258  raise OSError('CMA user id "%s" is unknown' % CMAUSERID)
259  for keyid in cmaidlist:
260  privatename = pyCryptCurve25519.key_id_to_filename(keyid, pyCryptFrame.PRIVATEKEY)
261  pubname = pyCryptCurve25519.key_id_to_filename(keyid, pyCryptFrame.PUBLICKEY)
262  # pylint doesn't understand about getent...
263  # pylint: disable=E1101
264  os.chown(pubname, userinfo.uid, userinfo.gid)
265  # pylint: disable=E1101
266  os.chown(privatename, userinfo.uid, userinfo.gid)
267  privatecount += 1
268  if privatecount > 1:
269  print ('SECURELY HIDE *private* key %s' % privatename)
270  extras.append(keyid)
271 @RegisterCommand
272 class neo4jpass(object):
273  'Generate and remember a new neo4j password'
274 
275  @staticmethod
276  def usage():
277  "reports usage for this sub-command"
278  return 'neo4jpass [optional-new-password]'
279 
280  @staticmethod
281  def execute(_store, _executor_context, otherargs, _flagoptions):
282  'Generate and remember a new neo4j password'
283  if os.geteuid() != 0 or len(otherargs) > 1:
284  return usage()
285  Neo4jCreds().update(newauth=otherargs[0] if len(otherargs) > 0 else None)
286 
287 
288 options = {'language':True, 'format':True, 'hostnames':False, 'ruleids': False}
289 def usage():
290  'Construct and print usage message'
291  argv = sys.argv
292 
293  optlist = ''
294  for opt in options:
295  optlist += "[--%s <%s>] " % (opt, opt)
296 
297  cmds = []
298  for cmd in commands.keys():
299  cmds.append(cmd)
300  cmds.sort()
301 
302  print >> sys.stderr, 'Usage: %s %ssub-command [sub-command-args]' % (argv[0], optlist)
303  print >> sys.stderr, ' Legal sub-command usages are:'
304  for cmd in cmds:
305  print >> sys.stderr, ' %s' % commands[cmd].usage()
306  print >> sys.stderr, '(all sub-commands must be run as root or %s)' % CMAUSERID
307  return 1
308 
309 # pylint too few public methods...
310 # pylint: disable=R0903
311 class DummyIO(object):
312  '''
313  A Dummy I/O object which has a config member...
314  '''
315  def __init__(self, config=None):
316  if config is None:
317  try:
318  config = ConfigFile(filename=CMAINITFILE)
319  except IOError:
320  config = ConfigFile()
321  self.config = config
322 
323 def dbsetup(readonly=False, url=None):
324  'Set up our connection to Neo4j'
325  if url is None:
326  url = 'localhost.com:7474'
327  Neo4jCreds().authenticate(url)
328  ourstore = Store(neo4j.Graph(), uniqueindexmap={}, classkeymap={})
329  CMAinit(DummyIO(), readonly=readonly, use_network=False)
330  for classname in GraphNode.classmap:
331  GraphNode.initclasstypeobj(ourstore, classname)
332  CMAdb.store = ourstore
333  return ourstore
334 
335 
336 def main(argv):
337  'Main program for command line tool'
338  ourstore = None
339  executor_context = None
340 
341  nodbcmds = {'genkeys', 'neo4jpass'}
342  rwcmds = {'loadqueries', 'loadbp'}
343  selected_options = {}
344  narg = 0
345  skipnext = False
346  for narg in range(1, len(argv)):
347  arg = argv[narg]
348  if skipnext:
349  skipnext = False
350  continue
351  if arg.startswith('--'):
352  skipnext = True
353  option = arg[2:]
354  if option not in options:
355  usage()
356  return 1
357  if options[option]:
358  selected_options[option] = argv[narg+1]
359  skipnext = True
360  else:
361  selected_options[option] = True
362  skipnext = False
363  else:
364  command=arg
365  break
366 
367  if len(argv) < 2 or command not in commands:
368  usage()
369  return 1
370  if command not in nodbcmds:
371  ourstore=dbsetup(readonly=(command not in rwcmds))
372  return commands[command].execute(ourstore, executor_context, sys.argv[narg+1:],
373  selected_options)
374 
375 
376 if __name__ == '__main__':
377  sys.exit(main(sys.argv))
WINEXPORT char * cryptcurve25519_gen_persistent_keypair(const char *giveitaname)
Create a persistent keypair and write it to disk Returns a MALLOCed string with the key id for the ke...
def __init__(self)
Definition: assimcli.py:179
def load_query_object(store, queryname)
Definition: assimcli.py:73
def __init__(self)
Definition: assimcli.py:217
def __init__(self)
Definition: assimcli.py:64
def RegisterCommand(classtoregister)
Definition: assimcli.py:53
def execute(store, executor_context, otherargs, flagoptions)
Definition: assimcli.py:189
def usage()
Definition: assimcli.py:289
def execute(store, executor_context, otherargs, flagoptions)
Definition: assimcli.py:154
def dbsetup(readonly=False, url=None)
Definition: assimcli.py:323
WINEXPORT void cryptcurve25519_cache_all_keypairs(void)
We read in and cache all the key pairs (or public keys) that we find in CRYPTKEYDIR.
def execute(_store, _executor_context, otherargs, _flagoptions)
Definition: assimcli.py:281
def execute(store, executor_context, otherargs, flagoptions)
Definition: assimcli.py:85
def main(argv)
Definition: assimcli.py:336
def execute(store, _executor_context, otherargs, _flagoptions)
Definition: assimcli.py:227
def __init__(self, config=None)
Definition: assimcli.py:315