Add support for TLS-enabled etcd

With this change CA certificate is saved to /opt/ccp/etc/tls/ca.pem, if that
file does not exist. Should be useful to avoid mounting CA certificate to each
job, pod, etc.

Change-Id: I574d64082e77f49024f49aa7b30c4f2f6cc044ac
Depends-On: Ib4b3ea4da7c1f641b9ab0223226348de5eac94df
This commit is contained in:
Aleksandr Mogylchenko 2017-01-18 17:23:01 +01:00
parent fbbb667354
commit d82aebcd82
2 changed files with 78 additions and 6 deletions

View File

@ -24,6 +24,7 @@ import six
VARIABLES = {}
GLOBALS_PATH = '/etc/ccp/globals/globals.json'
META_FILE = "/etc/ccp/meta/meta.json"
CACERT = "/opt/ccp/etc/tls/ca.pem"
WORKFLOW_PATH_TEMPLATE = '/etc/ccp/role/%s.json'
FILES_DIR = '/etc/ccp/files'
EXPORTS_DIR = '/etc/ccp/exports'
@ -291,24 +292,39 @@ def create_files(files):
@retry
def get_etcd_client():
if VARIABLES["security"]["tls"]["enabled"]:
LOG.debug("TLS is enabled for etcd, using encrypted connectivity")
scheme = "https"
ca_cert = CACERT
else:
scheme = "http"
ca_cert = None
etcd_machines = []
# if it's etcd container use local address because container is not
# accessible via service due failed readiness check
if VARIABLES["role_name"] in ["etcd", "etcd-leader-elector",
"etcd-watcher"]:
if VARIABLES["security"]["tls"]["enabled"]:
# If it's etcd container, connectivity goes over IP address, thus
# TLS connection will fail. Need to reuse non-TLS
# https://github.com/coreos/etcd/issues/4311
scheme = "http"
ca_cert = None
etcd_address = '127.0.0.1'
else:
etcd_address = VARIABLES["network_topology"]["private"]["address"]
etcd_machines.append(
(VARIABLES["network_topology"]["private"]["address"],
VARIABLES["etcd"]["client_port"]['cont']))
(etcd_address, VARIABLES["etcd"]["client_port"]['cont']))
else:
etcd_machines.append(
(address('etcd'), VARIABLES["etcd"]["client_port"]['cont'])
)
etcd_machines_str = " ".join(["%s:%d" % (h, p) for h, p in etcd_machines])
LOG.debug("Using the following etcd urls: \"%s\"", etcd_machines_str)
return etcd.Client(host=tuple(etcd_machines), allow_reconnect=True,
read_timeout=2)
read_timeout=2, protocol=scheme, ca_cert=ca_cert)
def check_dependence(dep, etcd_client):
@ -465,6 +481,16 @@ def get_variables(role_name):
return variables
def _get_ca_certificate():
name = CACERT
if not os.path.isfile(name):
with open(CACERT, 'w') as f:
f.write(VARIABLES['security']['tls']['ca_cert'])
LOG.info("CA certificated saved to %s", CACERT)
else:
LOG.info("CA file exists, not overwriting it")
def main():
action_parser = argparse.ArgumentParser(add_help=False)
action_parser.add_argument("action")
@ -476,6 +502,8 @@ def main():
VARIABLES = get_variables(args.role)
LOG.debug('Global variables:\n%s', VARIABLES)
if VARIABLES["security"]["tls"]["enabled"]:
_get_ca_certificate()
if args.action == "provision":
do_provision(args.role)
elif args.action == "status":

View File

@ -142,6 +142,11 @@ class TestGetETCDClient(base.TestCase):
"private": {
"address": "192.0.2.1"
}
},
"security": {
"tls": {
"enabled": False
}
}
}
with mock.patch("etcd.Client") as m_etcd:
@ -152,7 +157,9 @@ class TestGetETCDClient(base.TestCase):
m_etcd.assert_called_once_with(
host=(("192.0.2.1", 10042),),
allow_reconnect=True,
read_timeout=2)
read_timeout=2,
protocol='http',
ca_cert=None)
def test_get_etcd_client(self):
start_script.VARIABLES = {
@ -166,6 +173,11 @@ class TestGetETCDClient(base.TestCase):
"connection_attempts": 3,
"connection_delay": 0,
},
"security": {
"tls": {
"enabled": False
}
}
}
with mock.patch("etcd.Client") as m_etcd:
expected_value = object()
@ -175,7 +187,39 @@ class TestGetETCDClient(base.TestCase):
m_etcd.assert_called_once_with(
host=(('etcd.ccp.svc.cluster.local', 1234),),
allow_reconnect=True,
read_timeout=2)
read_timeout=2,
protocol='http',
ca_cert=None)
def test_get_secured_etcd_client(self):
start_script.VARIABLES = {
"role_name": "banana",
"namespace": "ccp",
"cluster_domain": 'cluster.local',
"etcd": {
"client_port": {
"cont": 1234
},
"connection_attempts": 3,
"connection_delay": 0,
},
"security": {
"tls": {
"enabled": True
}
}
}
with mock.patch("etcd.Client") as m_etcd:
expected_value = object()
m_etcd.return_value = expected_value
etcd_client = start_script.get_etcd_client()
self.assertIs(expected_value, etcd_client)
m_etcd.assert_called_once_with(
host=(('etcd.ccp.svc.cluster.local', 1234),),
allow_reconnect=True,
read_timeout=2,
protocol='https',
ca_cert='/opt/ccp/etc/tls/ca.pem')
def test_get_etcd_client_wrong(self):
start_script.VARIABLES = {