Skip to content

Commit

Permalink
WIP: Tenant SSL Cert Support
Browse files Browse the repository at this point in the history
This fix adds tenant SSL support to existing custom attributes.
User can provide barbican container ref in custom attributes
and haproxy parser then downloads the container/secrets
and populates the certificate.
Also, the keystone auth credentials need to specified in a
separate auth file whose path should be provided in
contrail-vrouter-agent.conf file. Renaming to file as
keystone_auth_cfg_file

Change-Id: I2b85733820031033a05dfc27cbfa4fa3a3485611
Partial-Bug: #1499903
  • Loading branch information
Varun Lodaya authored and Varun Lodaya committed Oct 6, 2015
1 parent 620211d commit 02d6e06
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 42 deletions.
3 changes: 2 additions & 1 deletion src/vnsw/agent/init/agent_param.cc
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,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");
}

void AgentParam::ParseNexthopServer() {
Expand Down
4 changes: 4 additions & 0 deletions src/vnsw/agent/init/agent_param.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ 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 nexthop_server_endpoint() const {
return nexthop_server_endpoint_;
Expand Down Expand Up @@ -384,6 +387,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_;
VmwareMode vmware_mode_;
// List of IP addresses on the compute node.
AddressList compute_node_address_list_;
Expand Down
6 changes: 6 additions & 0 deletions src/vnsw/agent/oper/netns_instance_adapter.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "oper/netns_instance_adapter.h"
#include "oper/service_instance.h"
#include "oper/instance_task.h"
#include "agent.h"
#include "init/agent_param.h"

InstanceTask* NetNSInstanceAdapter::CreateStartTask(const ServiceInstance::Properties &props, bool update) {
std::stringstream cmd_str;
Expand Down Expand Up @@ -36,6 +38,10 @@ 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 (update) {
Expand Down
2 changes: 2 additions & 0 deletions src/vnsw/opencontrail-vrouter-netns/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ sources = [
'opencontrail_vrouter_netns/haproxy_config.py',
'opencontrail_vrouter_netns/haproxy_process.py',
'opencontrail_vrouter_netns/haproxy_validator.py',
'opencontrail_vrouter_netns/haproxy_cert.py',
'opencontrail_vrouter_netns/keystone_auth.py',
'opencontrail_vrouter_netns/vrouter_docker.py',
'opencontrail_vrouter_netns/daemon_start.py',
'opencontrail_vrouter_netns/daemon_stop.py',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import json
import keystone_auth
import sys
import logging

class Barbican_Cert_Manager(object):
"""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 _get_barbican_entity(self, barbican_ep, auth_token,
entity_ref, metadata=True):
if metadata:
accept_data = 'application/json'
else:
accept_data = 'text/plain'

try:
headers = {
"Accept": "%s" % accept_data,
"X-Auth-Token": "%s" % auth_token
}
url = entity_ref
resp = keystone_auth._request(url, headers, 'GET')
if resp.status_code in range(200, 299):
if metadata:
return json.loads(resp.text)
else:
return resp.text
else:
logging.error("%s getting barbican entity %s" % \
(resp.text, url))
except Exception as e:
logging.error("%s getting barbican entity %s" % \
(str(e), url))
return None

def _validate_tls_secret(self, tls_container_ref):
try:
if self.identity:
#self.identity = keystone_auth.Identity()
container_detail = self._get_barbican_entity(\
self.identity.barbican_ep,
self.identity.auth_token,
entity_ref=tls_container_ref,
metadata=True)

if not container_detail:
return False

# Validate that secrets are stored plain text
for secret in container_detail['secret_refs']:
secret_meta_data = self._get_barbican_entity(\
self.identity.barbican_ep,
self.identity.auth_token,
entity_ref=secret['secret_ref'],
metadata=True)
if not secret_meta_data or secret_meta_data\
['content_types']['default'] != 'text/plain':
logging.error("Invalid secret format: %s" % \
secret_meta_data['content_types']['default'])
return False
return True
else:
return False
except Exception as e:
logging.error("%s while validating TLS Container" % str(e))
return False

def _populate_tls_pem(self, tls_container_ref):
try:
if self.identity:
#self.identity = keystone_auth.Identity()
container_detail = self._get_barbican_entity(\
self.identity.barbican_ep,
self.identity.auth_token,
entity_ref=tls_container_ref,
metadata=True)

if not container_detail:
return False

# Fetch the secrets stored in plain text
secret_text = ''
for secret in container_detail['secret_refs']:
secret_detail = self._get_barbican_entity(\
self.identity.barbican_ep,
self.identity.auth_token,
entity_ref=secret['secret_ref'],
metadata=False)
if secret_detail:
secret_text += secret_detail
secret_text += "\n"

return secret_text
else:
return None
except Exception as e:
logging.error("%s while populating SSL Pem file" % str(e))
return None
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import json
import os
import logging

def validate_custom_attributes(config, section):
def validate_custom_attributes(config, section, keystone_auth_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)

# Setup global definitions
PROTO_TCP = 'TCP'
PROTO_HTTP = 'HTTP'
PROTO_HTTPS = 'HTTPS'
Expand Down Expand Up @@ -38,7 +44,7 @@ def validate_custom_attributes(config, section):

HTTPS_PORT = 443

def build_config(conf_file):
def build_config(conf_file, keystone_auth_conf_file):
with open(conf_file) as data_file:
config = json.load(data_file)
conf_dir = os.path.dirname(conf_file)
Expand All @@ -47,13 +53,21 @@ def build_config(conf_file):
sock_path = conf_dir + '/haproxy.sock'
conf = _set_global_config(config, sock_path) + '\n\n'
conf += _set_defaults(config) + '\n\n'
conf += _set_frontend(config) + '\n\n'
conf += _set_frontend(config, conf_dir, keystone_auth_conf_file) + '\n\n'
conf += _set_backend(config) + '\n'
filename = conf_dir + '/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):
for key, value in custom_attributes.iteritems():
cmd = custom_attributes_dict['global'][key]['cmd']
conf.append(cmd % value)

res = "\n\t".join(conf)
return res

def _set_global_config(config, sock_path):
global_custom_attributes = validator(config, 'global')
maxconn = global_custom_attributes.pop('maxconn', None) \
Expand All @@ -77,11 +91,9 @@ def _set_global_config(config, sock_path):
'maxconn %d' % maxconn
]
conf.append('stats socket %s mode 0666 level user' % sock_path)
for key, value in global_custom_attributes.iteritems():
cmd = custom_attributes_dict['global'][key]['cmd']
conf.append(cmd % value)

return ("\n\t".join(conf))
return _construct_config_block(config, conf, "global", global_custom_attributes)


def _set_defaults(config):
default_custom_attributes = validator(config, 'default')
Expand All @@ -102,18 +114,21 @@ def _set_defaults(config):
'timeout server %d' % server_timeout,
]

for key, value in default_custom_attributes.iteritems():
cmd = custom_attributes_dict['default'][key]['cmd']
conf.append(cmd % value)
return _construct_config_block(config, conf, "default", default_custom_attributes)

return ("\n\t".join(conf))

def _set_frontend(config):
def _set_frontend(config, conf_dir, keystone_auth_conf_file):
port = config['vip']['port']
vip_custom_attributes = validator(config, 'vip')
vip_custom_attributes = validator(config, 'vip', keystone_auth_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)
else:
crt_file = config['ssl-crt']

if config['vip']['protocol'] == PROTO_HTTPS:
ssl = 'ssl crt %s no-sslv3' % config['ssl-crt']
ssl = 'ssl crt %s no-sslv3' % crt_file
conf = [
'frontend %s' % config['vip']['id'],
'option tcplog',
Expand All @@ -127,11 +142,7 @@ def _set_frontend(config):
config['vip']['protocol'] == PROTO_HTTPS:
conf.append('option forwardfor')

for key, value in vip_custom_attributes.iteritems():
cmd = custom_attributes_dict['vip'][key]['cmd']
conf.append(cmd % value)

return ("\n\t".join(conf))
return _construct_config_block(config, conf, "vip", vip_custom_attributes)

def _set_backend(config):
pool_custom_attributes = validator(config, 'pool')
Expand All @@ -143,10 +154,6 @@ def _set_backend(config):
if config['pool']['protocol'] == PROTO_HTTP:
conf.append('option forwardfor')

for key, value in pool_custom_attributes.iteritems():
cmd = custom_attributes_dict['pool'][key]['cmd']
conf.append(cmd % value)

server_suffix, monitor_conf = _set_health_monitor(config)
conf.extend(monitor_conf)
session_conf = _set_session_persistence(config)
Expand All @@ -161,11 +168,7 @@ def _set_backend(config):
server += ' cookie %d' % config['members'].index(member)
conf.append(server)

for key, value in pool_custom_attributes.iteritems():
cmd = custom_attributes_dict['pool'][key]['cmd']
conf.append(cmd % value)

return ("\n\t".join(conf))
return _construct_config_block(config, conf, "pool", pool_custom_attributes)

def _set_health_monitor(config):
for monitor in config['healthmonitors']:
Expand Down Expand Up @@ -216,3 +219,10 @@ def _get_codes(codes):
else:
response.add(code)
return response

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

return crt_filename
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ def stop_haproxy(conf_file, daemon_mode=False):
except Exception as e:
pass

def start_update_haproxy(conf_file, netns, daemon_mode=False):
def start_update_haproxy(conf_file, netns, daemon_mode=False,
keystone_auth_conf_file=None):
pool_id = os.path.split(os.path.dirname(conf_file))[1]
haproxy_cfg_file = haproxy_config.build_config(conf_file)
haproxy_cfg_file = haproxy_config.build_config(conf_file, \
keystone_auth_conf_file)
try:
if daemon_mode:
_start_haproxy_daemon(pool_id, netns, haproxy_cfg_file)
Expand Down

0 comments on commit 02d6e06

Please sign in to comment.