Navigation Menu

Skip to content

Commit

Permalink
Fix to support multiple cert managers with LBaaS
Browse files Browse the repository at this point in the history
This fix allows users to be able to use differet cert managers
with LBaaS custom attributes. It adds a Generic Cert Manager to
the code which can copy files from a specific folder.
Adding metaclass to cert_manager.
It also fixes the crt file name from generic to instance specific
to support multiple certs under the same folder.
Modified log formatter to provide file/line_nos.
Also, added few unittests to custom attribute code.
Closes-Bug: #1547645

Change-Id: I65cef1690cad6bad229e176615e18cad246334eb
  • Loading branch information
varun_lodaya committed Feb 26, 2016
1 parent 75df400 commit 2efe77a
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 78 deletions.
4 changes: 2 additions & 2 deletions src/vnsw/agent/init/agent_param.cc
Expand Up @@ -543,8 +543,8 @@ void AgentParam::ParseServiceInstance() {
"SERVICE-INSTANCE.netns_timeout");
GetValueFromTree<string>(si_lb_ssl_cert_path_,
"SERVICE-INSTANCE.lb_ssl_cert_path");
GetValueFromTree<string>(si_lb_keystone_auth_conf_path_,
"SERVICE-INSTANCE.lb_keystone_auth_conf_path");
GetValueFromTree<string>(si_lb_custom_attr_conf_path_,
"SERVICE-INSTANCE.lb_custom_attr_conf_path");
}

void AgentParam::ParseNexthopServer() {
Expand Down
6 changes: 3 additions & 3 deletions src/vnsw/agent/init/agent_param.h
Expand Up @@ -121,8 +121,8 @@ class AgentParam {
std::string si_lb_ssl_cert_path() const {
return si_lb_ssl_cert_path_;
}
std::string si_lb_keystone_auth_conf_path() const {
return si_lb_keystone_auth_conf_path_;
std::string si_lb_custom_attr_conf_path() const {
return si_lb_custom_attr_conf_path_;
}

std::string nexthop_server_endpoint() const {
Expand Down Expand Up @@ -405,7 +405,7 @@ class AgentParam {
int si_netns_workers_;
int si_netns_timeout_;
std::string si_lb_ssl_cert_path_;
std::string si_lb_keystone_auth_conf_path_;
std::string si_lb_custom_attr_conf_path_;
VmwareMode vmware_mode_;
// List of IP addresses on the compute node.
AddressList compute_node_address_list_;
Expand Down
19 changes: 17 additions & 2 deletions src/vnsw/agent/oper/instance_manager.cc
Expand Up @@ -99,12 +99,13 @@ class InstanceManager::NamespaceStaleCleaner {
//If Loadbalncer, delete the config files as well
if (prop.service_type == ServiceInstance::LoadBalancer) {
std::stringstream pathgen;
std::stringstream conf_path, json_path, sock_path;
std::stringstream conf_path, json_path, sock_path, crt_path;

pathgen << manager_->loadbalancer_config_path_ << prop.pool_id;
conf_path << pathgen.str() << ".haproxy.conf";
json_path << pathgen.str() << ".conf.json";
sock_path << pathgen.str() << ".haproxy.sock";
crt_path << pathgen.str() << ".crtbundle.pem";

boost::system::error_code error;
if (fs::exists(conf_path.str())) {
Expand All @@ -130,6 +131,14 @@ class InstanceManager::NamespaceStaleCleaner {
<< error.message());
}
}

if (fs::exists(crt_path.str())) {
fs::remove_all(crt_path.str(), error);
if (error) {
LOG(ERROR, "Stale loadbalancer certificate file delete error"
<< error.message());
}
}
}
}
}
Expand Down Expand Up @@ -764,10 +773,11 @@ void InstanceManager::LoadbalancerObserver(
lb_config_->GenerateConfig(pathgen.str(), loadbalancer->uuid(),
*loadbalancer->properties());
} else {
std::stringstream conf_path, json_path, sock_path;
std::stringstream conf_path, json_path, sock_path, crt_path;
conf_path << pathgen.str() << ".haproxy.conf";
json_path << pathgen.str() << ".conf.json";
sock_path << pathgen.str() << ".haproxy.sock";
crt_path << pathgen.str() << ".crtbundle.pem";

boost::filesystem::path conf_file(conf_path.str());
if (boost::filesystem::exists(conf_file, error)) {
Expand All @@ -783,6 +793,11 @@ void InstanceManager::LoadbalancerObserver(
if (boost::filesystem::exists(json_file, error)) {
boost::filesystem::remove_all(json_path.str(), error);
}

boost::filesystem::path crt_file(crt_path.str());
if (boost::filesystem::exists(crt_file, error)) {
boost::filesystem::remove_all(crt_path.str(), error);
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/vnsw/agent/oper/netns_instance_adapter.cc
Expand Up @@ -38,9 +38,9 @@ InstanceTask* NetNSInstanceAdapter::CreateStartTask(const ServiceInstance::Prope
cmd_str << " --cfg-file " << loadbalancer_config_path_ <<
props.pool_id << ".conf.json";
cmd_str << " --pool-id " << props.pool_id;
if (!agent_->params()->si_lb_keystone_auth_conf_path().empty()) {
cmd_str << " --keystone-auth-cfg-file " <<
agent_->params()->si_lb_keystone_auth_conf_path();
if (!agent_->params()->si_lb_custom_attr_conf_path().empty()) {
cmd_str << " --custom-attr-cfg-file " <<
agent_->params()->si_lb_custom_attr_conf_path();
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/vnsw/opencontrail-vrouter-netns/SConscript
Expand Up @@ -33,7 +33,8 @@ sources = [
'opencontrail_vrouter_netns/linux/utils.py',
'opencontrail_vrouter_netns/tests/__init__.py',
'opencontrail_vrouter_netns/tests/test_vrouter_netns.py',
'opencontrail_vrouter_netns/tests/test_vrouter_docker.py'
'opencontrail_vrouter_netns/tests/test_vrouter_docker.py',
'opencontrail_vrouter_netns/tests/test_lbaas_custom_attributes.py'
]

cd_cmd = 'cd ' + Dir('.').path + ' && '
Expand Down
@@ -1,16 +1,48 @@
import json
import keystone_auth
import sys
import logging
import os
import requests
import abc
import six

class Barbican_Cert_Manager(object):
@six.add_metaclass(abc.ABCMeta)
class Cert_Manager(object):
"""Class to download certs from specific
drivers mentioned in the conf_file"""
def __init__(self):
pass

def _request(self, url, headers=None, body=None, request_type=None):
try:
if request_type == 'PUT':
encoded_body = json.dumps(body)
return requests.put(url, headers=headers, data=encoded_body)
elif request_type == 'POST':
encoded_body = json.dumps(body)
return requests.post(url, headers=headers, data=encoded_body)
else:
return requests.get(url, headers=headers)

except Exception as e:
logging.error("Failed sending request to keystone")
return None

@abc.abstractmethod
def _validate_tls_secret(self, tls_container_ref):
pass

@abc.abstractmethod
def _populate_tls_pem(self, tls_container_ref):
pass

class Barbican_Cert_Manager(Cert_Manager):
"""Class to download certs from barbican and
populate the pem file as required by HAProxy
"""
def __init__(self, keystone_auth_conf_file):
self.identity = keystone_auth.Identity(keystone_auth_conf_file)
if not self.identity:
raise Exception()
def __init__(self, identity=None):
super(Barbican_Cert_Manager, self).__init__()
self.identity = identity

def _get_barbican_entity(self, barbican_ep, auth_token,
entity_ref, metadata=True):
Expand All @@ -25,7 +57,7 @@ def _get_barbican_entity(self, barbican_ep, auth_token,
"X-Auth-Token": "%s" % auth_token
}
url = entity_ref
resp = keystone_auth._request(url, headers, 'GET')
resp = self._request(url, headers, 'GET')
if resp.status_code in range(200, 299):
if metadata:
return json.loads(resp.text)
Expand Down Expand Up @@ -102,3 +134,32 @@ def _populate_tls_pem(self, tls_container_ref):
except Exception as e:
logging.error("%s while populating SSL Pem file" % str(e))
return None


class Generic_Cert_Manager(Cert_Manager):
"""Class to download certs from Generic Cert Manager and
populate the pem file as required by HAProxy
"""
def __init__(self, identity=None):
super(Generic_Cert_Manager, self).__init__()

def _validate_tls_secret(self, tls_container_ref):
if tls_container_ref is None:
return False

# Check if the file exists
if not os.path.isfile(tls_container_ref):
return False

# Check if file is readable
if not os.access(tls_container_ref, os.R_OK):
return False

return True

def _populate_tls_pem(self, tls_container_ref):
secret_text = ''
with open(tls_container_ref) as tls_container:
secret_text = tls_container.read()

return secret_text
Expand Up @@ -2,19 +2,20 @@
import os
import logging

def validate_custom_attributes(config, section, keystone_auth_conf_file=None):
def validate_custom_attributes(config, section, custom_attr_conf_file=None):
return {}

try:
from haproxy_validator import validate_custom_attributes as validator
from haproxy_validator import custom_attributes_dict
from haproxy_cert import Barbican_Cert_Manager
except ImportError:
validator = validate_custom_attributes
custom_attributes_dict = {}

# Setup logger
logging.basicConfig(filename='/var/log/contrail/haproxy_parse.log', level=logging.WARNING)
FORMAT="[%(filename)s:%(lineno)s] %(message)s"
logging.basicConfig(filename='/var/log/contrail/haproxy_parse.log',
level=logging.WARNING, format=FORMAT)

# Setup global definitions
PROTO_TCP = 'TCP'
Expand Down Expand Up @@ -44,7 +45,7 @@ def validate_custom_attributes(config, section, keystone_auth_conf_file=None):

HTTPS_PORT = 443

def build_config(pool_id, conf_file, keystone_auth_conf_file):
def build_config(pool_id, conf_file, custom_attr_conf_file=None):
with open(conf_file) as data_file:
config = json.load(data_file)
conf_dir = os.path.dirname(conf_file)
Expand All @@ -53,14 +54,15 @@ def build_config(pool_id, conf_file, keystone_auth_conf_file):
sock_path = conf_dir + '/' + pool_id + '.haproxy.sock'
conf = _set_global_config(config, sock_path) + '\n\n'
conf += _set_defaults(config) + '\n\n'
conf += _set_frontend(config, conf_dir, keystone_auth_conf_file) + '\n\n'
conf += _set_frontend(config, conf_dir, custom_attr_conf_file) + '\n\n'
conf += _set_backend(config) + '\n'
filename = conf_dir + '/' + pool_id + '.haproxy.conf'
conf_file = open(filename, 'w')
conf_file.write(conf)
return filename

def _construct_config_block(lb_config, conf, custom_attr_section, custom_attributes):
def _construct_config_block(lb_config, conf, custom_attr_section,
custom_attributes):
for key, value in custom_attributes.iteritems():
cmd = custom_attributes_dict[custom_attr_section][key]['cmd']
conf.append(cmd % value)
Expand Down Expand Up @@ -92,7 +94,8 @@ def _set_global_config(config, sock_path):
]
conf.append('stats socket %s mode 0666 level user' % sock_path)

return _construct_config_block(config, conf, "global", global_custom_attributes)
return _construct_config_block(config, conf, "global",
global_custom_attributes)


def _set_defaults(config):
Expand All @@ -114,16 +117,17 @@ def _set_defaults(config):
'timeout server %d' % server_timeout,
]

return _construct_config_block(config, conf, "default", default_custom_attributes)
return _construct_config_block(config, conf, "default",
default_custom_attributes)

def _set_frontend(config, conf_dir, keystone_auth_conf_file):
def _set_frontend(config, conf_dir, custom_attr_conf_file=None):
port = config['vip']['port']
vip_custom_attributes = validator(config, 'vip', keystone_auth_conf_file)
vip_custom_attributes = validator(config, 'vip', custom_attr_conf_file)
ssl = ''

if 'tls_container' in vip_custom_attributes:
data = vip_custom_attributes.pop('tls_container', None)
crt_file = _populate_pem_file(data, conf_dir)
crt_file = _populate_pem_file(data, conf_dir, config['pool']['id'])
else:
crt_file = config['ssl-crt']

Expand Down Expand Up @@ -220,8 +224,8 @@ def _get_codes(codes):
response.add(code)
return response

def _populate_pem_file(data, conf_dir):
crt_filename = conf_dir + '/crtbundle.pem'
def _populate_pem_file(data, conf_dir, pool_id):
crt_filename = conf_dir + '/' + pool_id + '.crtbundle.pem'
with open(crt_filename, 'w+') as outfile:
outfile.write(data)

Expand Down
Expand Up @@ -17,10 +17,10 @@ def stop_haproxy(conf_file, daemon_mode=False):
pass

def start_update_haproxy(conf_file, netns, daemon_mode=False,
keystone_auth_conf_file=None):
custom_attr_conf_file=None):
pool_id = conf_file.split('.')[0].split('/')[-1]
haproxy_cfg_file = haproxy_config.build_config(pool_id, conf_file, \
keystone_auth_conf_file)
custom_attr_conf_file)
try:
if daemon_mode:
_start_haproxy_daemon(netns, haproxy_cfg_file)
Expand Down
@@ -1,7 +1,10 @@
import logging
import inspect
from haproxy_cert import Barbican_Cert_Manager
import ConfigParser
import keystone_auth
import os
import sys
import haproxy_cert

class CustomAttr(object):
"""This type handles non-flat data-types like
Expand All @@ -18,9 +21,48 @@ def post_validation(self, conf_list):
return

class CustomAttrTlsContainer(CustomAttr):
def __init__(self, keystone_auth_conf_file, key, value):
def __init__(self, key, value, custom_attr_conf_file=None):
super(CustomAttrTlsContainer, self).__init__(key, value)
self.cert_manager = Barbican_Cert_Manager(keystone_auth_conf_file)
if not custom_attr_conf_file:
logging.error("Missing custom attr conf file name in vrouter-agent.conf")
raise Exception()

if not self._read_config(custom_attr_conf_file):
logging.error("Error reading %s" % custom_attr_conf_file)
raise Exception()

cert_dict = self._parse_args(self._config, 'CERT')
if not cert_dict or not 'cert_manager' in cert_dict:
logging.error("Missing CERT section")
raise Exception()

if cert_dict['cert_manager'] == 'Barbican_Cert_Manager':
# Make sure keystone credentials are present
auth_dict = self._parse_args(self._config, 'KEYSTONE')
if not auth_dict:
logging.error("Missing KEYSTONE section")
raise Exception()
identity = keystone_auth.Identity(auth_dict)
else:
identity = None

self.cert_manager = getattr(haproxy_cert,
cert_dict['cert_manager'])(identity=identity)

def _read_config(self, conf_file):
config = ConfigParser.ConfigParser()
if not len(config.read(conf_file)):
return False
else:
self._config = config
return True

def _parse_args(self, config, section):
try:
return dict(config.items(section))
except Exception as e:
logging.error(str(e))
return None

def validate(self):
if self._key != 'tls_container':
Expand Down Expand Up @@ -118,7 +160,7 @@ def post_validation(self):
'pool': {},
}

def validate_custom_attributes(config, section, keystone_auth_conf_file=None):
def validate_custom_attributes(config, section, custom_attr_conf_file=None):
section_dict = {}
if 'custom-attributes' in config and section in custom_attributes_dict:
custom_attributes = config['custom-attributes']
Expand Down Expand Up @@ -152,8 +194,8 @@ def validate_custom_attributes(config, section, keystone_auth_conf_file=None):
logging.info("Skipping key: %s, value: %s due to" \
"validation failure" % (key, value))
elif inspect.isclass(type_attr):
new_custom_attr = type_attr(keystone_auth_conf_file, \
key, value)
new_custom_attr = type_attr(key, value,
custom_attr_conf_file)
if new_custom_attr.validate():
value = new_custom_attr.post_validation()
section_dict.update({key:value})
Expand Down

0 comments on commit 2efe77a

Please sign in to comment.