Add patronictl -k/--insecure flag and suport for restapi cert (#790)

Fixes https://github.com/zalando/patroni/issues/785
This commit is contained in:
wilfriedroset
2018-08-29 16:08:13 +02:00
committed by Alexander Kukushkin
parent 0e13677880
commit 0136f252ab
6 changed files with 50 additions and 5 deletions

View File

@@ -146,6 +146,13 @@ REST API
- **certfile**: Specifies the file with the certificate in the PEM format. If the certfile is not specified or is left empty, the API server will work without SSL.
- **keyfile**: Specifies the file with the secret key in the PEM format.
CTL
---
- **Optional**:
- **insecure**: Allow connections to REST API without verifying SSL certs.
- **cacert**: Specifices the file with the CA_BUNDLE file or directory with certificates of trusted CAs to use while verifying REST API SSL certs.
- **certfile**: Specifies the file with the certificate in the PEM format to use while verifying REST API SSL certs. If not provided patronictl will use the value provided for REST API "certfile" parameter.
ZooKeeper
----------
- **hosts**: list of ZooKeeper cluster members in format: ['host1:port1', 'host2:port2', 'etc...'].

View File

@@ -103,15 +103,20 @@ option_watch = click.option('-W', is_flag=True, help='Auto update the screen eve
option_force = click.option('--force', is_flag=True, help='Do not ask for confirmation at any point')
arg_cluster_name = click.argument('cluster_name', required=False,
default=lambda: click.get_current_context().obj.get('scope'))
option_insecure = click.option('-k', '--insecure', is_flag=True, help='Allow connections to SSL sites without certs')
@click.group()
@click.option('--config-file', '-c', help='Configuration file', default=CONFIG_FILE_PATH)
@click.option('--dcs', '-d', help='Use this DCS', envvar='DCS')
@option_insecure
@click.pass_context
def ctl(ctx, config_file, dcs):
def ctl(ctx, config_file, dcs, insecure):
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=os.environ.get('LOGLEVEL', 'WARNING'))
logging.captureWarnings(True) # Capture eventual SSL warning
ctx.obj = load_config(config_file, dcs)
# backward compatibility for configuration file where ctl section is not define
ctx.obj.setdefault('ctl', {})['insecure'] = ctx.obj.get('ctl', {}).get('insecure') or insecure
def get_dcs(config, scope):
@@ -129,6 +134,7 @@ def auth_header(config):
def request_patroni(member, request_type, endpoint, content=None, headers=None):
ctx = click.get_current_context() # the current click context
headers = headers or {}
url_parts = urlparse(member.api_url)
logging.debug(url_parts)
@@ -137,8 +143,21 @@ def request_patroni(member, request_type, endpoint, content=None, headers=None):
url = '{0}://{1}/{2}'.format(url_parts.scheme, url_parts.netloc, endpoint)
insecure = ctx.obj.get('ctl', {}).get('insecure', False)
# Get certfile if any from several configuration namespace
cert = ctx.obj.get('ctl', {}).get('cacert') or \
ctx.obj.get('restapi', {}).get('cacert') or \
ctx.obj.get('restapi', {}).get('certfile')
# In the case we specificaly disable SSL cert verification we don't want to have the warning
if insecure:
verify = False
elif cert:
verify = cert
else:
verify = True
return getattr(requests, request_type)(url, headers=headers,
data=json.dumps(content) if content else None, timeout=60)
data=json.dumps(content) if content else None, timeout=60,
verify=verify)
def print_output(columns, rows=None, alignment=None, fmt='pretty', header=True, delimiter='\t'):
@@ -903,7 +922,8 @@ def toggle_pause(config, cluster_name, paused, wait):
for member in members:
try:
r = request_patroni(member, 'patch', 'config', {'pause': paused or None}, auth_header(config))
except Exception:
except Exception as err:
logging.warning(str(err))
logging.warning('Member %s is not accessible', member.name)
continue

View File

@@ -11,6 +11,11 @@ restapi:
# username: username
# password: password
# ctl:
# insecure: false # Allow connections to SSL sites without certs
# certfile: /etc/ssl/certs/ssl-cert-snakeoil.pem
# cacert: /etc/ssl/certs/ssl-cacert-snakeoil.pem
etcd:
host: 127.0.0.1:2379

View File

@@ -11,6 +11,11 @@ restapi:
# username: username
# password: password
# ctl:
# insecure: false # Allow connections to SSL sites without certs
# certfile: /etc/ssl/certs/ssl-cert-snakeoil.pem
# cacert: /etc/ssl/certs/ssl-cacert-snakeoil.pem
etcd:
host: 127.0.0.1:2379

View File

@@ -11,6 +11,11 @@ restapi:
username: username
password: password
# ctl:
# insecure: false # Allow connections to SSL sites without certs
# certfile: /etc/ssl/certs/ssl-cert-snakeoil.pem
# cacert: /etc/ssl/certs/ssl-cacert-snakeoil.pem
etcd:
host: 127.0.0.1:2379

View File

@@ -345,8 +345,11 @@ class TestCtl(unittest.TestCase):
@patch('requests.post', Mock(side_effect=requests.exceptions.ConnectionError('foo')))
def test_request_patroni(self):
member = get_cluster_initialized_with_leader().leader.member
self.assertRaises(requests.exceptions.ConnectionError, request_patroni, member, 'post', 'dummy', {})
context = {'restapi': {'keyfile': '/etc/patroni/key.pem', 'certfile': 'cert.pem'}}
with patch('click.get_current_context') as mock_context:
mock_context.return_value.obj = context
member = get_cluster_initialized_with_leader().leader.member
self.assertRaises(requests.exceptions.ConnectionError, request_patroni, member, 'post', 'dummy', {})
def test_ctl(self):
self.runner.invoke(ctl, ['list'])