Revision 45bc4635

b/Makefile.am
395 395
	doc/design-partitioned.rst \
396 396
	doc/design-query-splitting.rst \
397 397
	doc/design-query2.rst \
398
	doc/design-remote-commands.rst \
399 398
	doc/design-resource-model.rst \
399
	doc/design-restricted-commands.rst \
400 400
	doc/design-shared-storage.rst \
401 401
	doc/design-monitoring-agent.rst \
402 402
	doc/design-virtual-clusters.rst \
b/doc/design-2.7.rst
6 6

  
7 7
- :doc:`design-bulk-create`
8 8
- :doc:`design-opportunistic-locking`
9
- :doc:`design-remote-commands`
9
- :doc:`design-restricted-commands`
10 10
- :doc:`design-node-add`
11 11
- :doc:`design-virtual-clusters`
12 12
- :doc:`design-network`
/dev/null
1
Design for executing commands via RPC
2
=====================================
3

  
4
.. contents:: :depth: 3
5

  
6

  
7
Current state and shortcomings
8
------------------------------
9

  
10
We have encountered situations where a node was no longer responding to
11
attempts at connecting via SSH or SSH became unavailable through other
12
means. Quite often the node daemon is still available, even in
13
situations where there's little free memory. The latter is due to the
14
node daemon being locked into main memory using ``mlock(2)``.
15

  
16
Since the node daemon does not allow the execution of arbitrary
17
commands, quite often the only solution left was either to attempt a
18
powercycle request via said node daemon or to physically reset the node.
19

  
20

  
21
Proposed changes
22
----------------
23

  
24
The goal of this design is to allow the execution of non-arbitrary
25
commands via RPC requests. Since this can be dangerous in case the
26
cluster certificate (``server.pem``) is leaked, some precautions need to
27
be taken:
28

  
29
- No parameters may be passed
30
- No absolute or relative path may be passed, only a filename
31
- Executable must reside in ``/etc/ganeti/remote-commands``, which must
32
  be owned by root:root and have mode 0755 or stricter
33
  - Must be regular files or symlinks
34
  - Must be executable by root:root
35

  
36
There shall be no way to list available commands or to retrieve an
37
executable's contents. The result from a request to execute a specific
38
command will either be its output and exit code, or a generic error
39
message. Only the receiving node's log files shall contain information
40
as to why executing the command failed.
41

  
42
To slow down dictionary attacks on command names in case an attacker
43
manages to obtain a copy of ``server.pem``, a system-wide, file-based
44
lock is acquired before verifying the command name and its executable.
45
If a command can not be executed for some reason, the lock is only
46
released with a delay of several seconds, after which the generic error
47
message will be returned to the caller.
48

  
49
At first, remote commands will not be made available through the
50
:doc:`remote API <rapi>`, though that could be done at a later point
51
(with a separate password).
52

  
53
On the command line, a new sub-command will be added to the ``gnt-node``
54
script.
55

  
56
.. vim: set textwidth=72 :
57
.. Local Variables:
58
.. mode: rst
59
.. fill-column: 72
60
.. End:
b/doc/design-restricted-commands.rst
1
Design for executing commands via RPC
2
=====================================
3

  
4
.. contents:: :depth: 3
5

  
6

  
7
Current state and shortcomings
8
------------------------------
9

  
10
We have encountered situations where a node was no longer responding to
11
attempts at connecting via SSH or SSH became unavailable through other
12
means. Quite often the node daemon is still available, even in
13
situations where there's little free memory. The latter is due to the
14
node daemon being locked into main memory using ``mlock(2)``.
15

  
16
Since the node daemon does not allow the execution of arbitrary
17
commands, quite often the only solution left was either to attempt a
18
powercycle request via said node daemon or to physically reset the node.
19

  
20

  
21
Proposed changes
22
----------------
23

  
24
The goal of this design is to allow the execution of non-arbitrary
25
commands via RPC requests. Since this can be dangerous in case the
26
cluster certificate (``server.pem``) is leaked, some precautions need to
27
be taken:
28

  
29
- No parameters may be passed
30
- No absolute or relative path may be passed, only a filename
31
- Executable must reside in ``/etc/ganeti/restricted-commands``, which must
32
  be owned by root:root and have mode 0755 or stricter
33
  - Must be regular files or symlinks
34
  - Must be executable by root:root
35

  
36
There shall be no way to list available commands or to retrieve an
37
executable's contents. The result from a request to execute a specific
38
command will either be its output and exit code, or a generic error
39
message. Only the receiving node's log files shall contain information
40
as to why executing the command failed.
41

  
42
To slow down dictionary attacks on command names in case an attacker
43
manages to obtain a copy of ``server.pem``, a system-wide, file-based
44
lock is acquired before verifying the command name and its executable.
45
If a command can not be executed for some reason, the lock is only
46
released with a delay of several seconds, after which the generic error
47
message will be returned to the caller.
48

  
49
At first, restricted commands will not be made available through the
50
:doc:`remote API <rapi>`, though that could be done at a later point
51
(with a separate password).
52

  
53
On the command line, a new sub-command will be added to the ``gnt-node``
54
script.
55

  
56
.. vim: set textwidth=72 :
57
.. Local Variables:
58
.. mode: rst
59
.. fill-column: 72
60
.. End:
b/doc/index.rst
51 51
   design-opportunistic-locking.rst
52 52
   design-ovf-support.rst
53 53
   design-query2.rst
54
   design-remote-commands.rst
54
   design-restricted-commands.rst
55 55
   design-shared-storage.rst
56 56
   design-virtual-clusters.rst
57 57
   design-network.rst
b/lib/backend.py
1 1
#
2 2
#
3 3

  
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5 5
#
6 6
# This program is free software; you can redistribute it and/or modify
7 7
# it under the terms of the GNU General Public License as published by
......
88 88
_MASTER_START = "start"
89 89
_MASTER_STOP = "stop"
90 90

  
91
#: Maximum file permissions for remote command directory and executables
91
#: Maximum file permissions for restricted command directory and executables
92 92
_RCMD_MAX_MODE = (stat.S_IRWXU |
93 93
                  stat.S_IRGRP | stat.S_IXGRP |
94 94
                  stat.S_IROTH | stat.S_IXOTH)
95 95

  
96
#: Delay before returning an error for remote commands
96
#: Delay before returning an error for restricted commands
97 97
_RCMD_INVALID_DELAY = 10
98 98

  
99
#: How long to wait to acquire lock for remote commands (shorter than
99
#: How long to wait to acquire lock for restricted commands (shorter than
100 100
#: L{_RCMD_INVALID_DELAY}) to reduce blockage of noded forks when many
101 101
#: command requests arrive
102 102
_RCMD_LOCK_TIMEOUT = _RCMD_INVALID_DELAY * 0.8
......
3672 3672

  
3673 3673

  
3674 3674
def _VerifyRestrictedCmdName(cmd):
3675
  """Verifies a remote command name.
3675
  """Verifies a restricted command name.
3676 3676

  
3677 3677
  @type cmd: string
3678 3678
  @param cmd: Command name
......
3694 3694

  
3695 3695

  
3696 3696
def _CommonRestrictedCmdCheck(path, owner):
3697
  """Common checks for remote command file system directories and files.
3697
  """Common checks for restricted command file system directories and files.
3698 3698

  
3699 3699
  @type path: string
3700 3700
  @param path: Path to check
......
3724 3724

  
3725 3725

  
3726 3726
def _VerifyRestrictedCmdDirectory(path, _owner=None):
3727
  """Verifies remote command directory.
3727
  """Verifies restricted command directory.
3728 3728

  
3729 3729
  @type path: string
3730 3730
  @param path: Path to check
......
3745 3745

  
3746 3746

  
3747 3747
def _VerifyRestrictedCmd(path, cmd, _owner=None):
3748
  """Verifies a whole remote command and returns its executable filename.
3748
  """Verifies a whole restricted command and returns its executable filename.
3749 3749

  
3750 3750
  @type path: string
3751
  @param path: Directory containing remote commands
3751
  @param path: Directory containing restricted commands
3752 3752
  @type cmd: string
3753 3753
  @param cmd: Command name
3754 3754
  @rtype: tuple; (boolean, string)
......
3774 3774
                          _verify_dir=_VerifyRestrictedCmdDirectory,
3775 3775
                          _verify_name=_VerifyRestrictedCmdName,
3776 3776
                          _verify_cmd=_VerifyRestrictedCmd):
3777
  """Performs a number of tests on a remote command.
3777
  """Performs a number of tests on a restricted command.
3778 3778

  
3779 3779
  @type path: string
3780
  @param path: Directory containing remote commands
3780
  @param path: Directory containing restricted commands
3781 3781
  @type cmd: string
3782 3782
  @param cmd: Command name
3783 3783
  @return: Same as L{_VerifyRestrictedCmd}
......
3804 3804
                     _prepare_fn=_PrepareRestrictedCmd,
3805 3805
                     _runcmd_fn=utils.RunCmd,
3806 3806
                     _enabled=constants.ENABLE_RESTRICTED_COMMANDS):
3807
  """Executes a remote command after performing strict tests.
3807
  """Executes a restricted command after performing strict tests.
3808 3808

  
3809 3809
  @type cmd: string
3810 3810
  @param cmd: Command name
......
3813 3813
  @raise RPCFail: In case of an error
3814 3814

  
3815 3815
  """
3816
  logging.info("Preparing to run remote command '%s'", cmd)
3816
  logging.info("Preparing to run restricted command '%s'", cmd)
3817 3817

  
3818 3818
  if not _enabled:
3819
    _Fail("Remote commands disabled at configure time")
3819
    _Fail("Restricted commands disabled at configure time")
3820 3820

  
3821 3821
  lock = None
3822 3822
  try:
......
3844 3844
      # Do not include original error message in returned error
3845 3845
      _Fail("Executing command '%s' failed" % cmd)
3846 3846
    elif cmdresult.failed or cmdresult.fail_reason:
3847
      _Fail("Remote command '%s' failed: %s; output: %s",
3847
      _Fail("Restricted command '%s' failed: %s; output: %s",
3848 3848
            cmd, cmdresult.fail_reason, cmdresult.output)
3849 3849
    else:
3850 3850
      return cmdresult.output
b/test/py/ganeti.backend_unittest.py
1 1
#!/usr/bin/python
2 2
#
3 3

  
4
# Copyright (C) 2010 Google Inc.
4
# Copyright (C) 2010, 2013 Google Inc.
5 5
#
6 6
# This program is free software; you can redistribute it and/or modify
7 7
# it under the terms of the GNU General Public License as published by
......
423 423
                               _sleep_fn=sleep_fn, _prepare_fn=prepare_fn,
424 424
                               _enabled=True)
425 425
    except backend.RPCFail, err:
426
      self.assertTrue(str(err).startswith("Remote command 'test3079' failed:"))
426
      self.assertTrue(str(err).startswith("Restricted command 'test3079'"
427
                                          " failed:"))
427 428
      self.assertTrue("stderr406328567" in str(err),
428 429
                      msg="Error did not include output")
429 430
    else:
......
477 478
                               _runcmd_fn=NotImplemented,
478 479
                               _enabled=False)
479 480
    except backend.RPCFail, err:
480
      self.assertEqual(str(err), "Remote commands disabled at configure time")
481
      self.assertEqual(str(err),
482
                       "Restricted commands disabled at configure time")
481 483
    else:
482 484
      self.fail("Did not raise exception")
483 485

  

Also available in: Unified diff