Add file synchronization via unison/ssh.
This commit is contained in:
commit
b70f568191
@ -112,3 +112,7 @@ options:
|
|||||||
default: "false"
|
default: "false"
|
||||||
type: string
|
type: string
|
||||||
description: "Enable PKI token signing (Grizzly and beyond)"
|
description: "Enable PKI token signing (Grizzly and beyond)"
|
||||||
|
https-service-endpoints:
|
||||||
|
default: "False"
|
||||||
|
type: string
|
||||||
|
description: "Manage SSL certificates for all service endpoints."
|
||||||
|
@ -3,12 +3,16 @@
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
from lib.openstack_common import *
|
from lib.openstack_common import *
|
||||||
|
import lib.unison as unison
|
||||||
|
|
||||||
config = config_get()
|
config = config_get()
|
||||||
|
|
||||||
packages = "keystone python-mysqldb pwgen haproxy python-jinja2"
|
packages = "keystone python-mysqldb pwgen haproxy python-jinja2 openssl unison"
|
||||||
service = "keystone"
|
service = "keystone"
|
||||||
|
|
||||||
# used to verify joined services are valid openstack components.
|
# used to verify joined services are valid openstack components.
|
||||||
@ -81,6 +85,11 @@ def install_hook():
|
|||||||
execute("service keystone stop", echo=True)
|
execute("service keystone stop", echo=True)
|
||||||
execute("keystone-manage db_sync")
|
execute("keystone-manage db_sync")
|
||||||
execute("service keystone start", echo=True)
|
execute("service keystone start", echo=True)
|
||||||
|
|
||||||
|
# ensure /var/lib/keystone is g+wrx for peer relations that
|
||||||
|
# may be syncing data there via SSH_USER.
|
||||||
|
execute("chmod -R g+wrx /var/lib/keystone/")
|
||||||
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
ensure_initial_admin(config)
|
ensure_initial_admin(config)
|
||||||
|
|
||||||
@ -97,6 +106,7 @@ def db_changed():
|
|||||||
'private-address' not in relation_data):
|
'private-address' not in relation_data):
|
||||||
juju_log("private-address or password not set. Peer not ready, exit 0")
|
juju_log("private-address or password not set. Peer not ready, exit 0")
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
update_config_block('sql', connection="mysql://%s:%s@%s/%s" %
|
update_config_block('sql', connection="mysql://%s:%s@%s/%s" %
|
||||||
(config["database-user"],
|
(config["database-user"],
|
||||||
relation_data["password"],
|
relation_data["password"],
|
||||||
@ -125,6 +135,21 @@ def db_changed():
|
|||||||
(id, unit))
|
(id, unit))
|
||||||
identity_changed(relation_id=id, remote_unit=unit)
|
identity_changed(relation_id=id, remote_unit=unit)
|
||||||
|
|
||||||
|
def ensure_valid_service(service):
|
||||||
|
if service not in valid_services.keys():
|
||||||
|
juju_log("WARN: Invalid service requested: '%s'" % service)
|
||||||
|
realtion_set({ "admin_token": -1 })
|
||||||
|
return
|
||||||
|
|
||||||
|
def add_endpoint(region, service, public_url, admin_url, internal_url):
|
||||||
|
desc = valid_services[service]["desc"]
|
||||||
|
service_type = valid_services[service]["type"]
|
||||||
|
create_service_entry(service, service_type, desc)
|
||||||
|
create_endpoint_template(region=region, service=service,
|
||||||
|
public_url=public_url,
|
||||||
|
admin_url=admin_url,
|
||||||
|
internal_url=internal_url)
|
||||||
|
|
||||||
def identity_joined():
|
def identity_joined():
|
||||||
""" Do nothing until we get information about requested service """
|
""" Do nothing until we get information about requested service """
|
||||||
pass
|
pass
|
||||||
@ -170,13 +195,13 @@ def identity_changed(relation_id=None, remote_unit=None):
|
|||||||
'internal_url'])
|
'internal_url'])
|
||||||
if single.issubset(settings):
|
if single.issubset(settings):
|
||||||
# other end of relation advertised only one endpoint
|
# other end of relation advertised only one endpoint
|
||||||
|
|
||||||
if 'None' in [v for k,v in settings.iteritems()]:
|
if 'None' in [v for k,v in settings.iteritems()]:
|
||||||
# Some backend services advertise no endpoint but require a
|
# Some backend services advertise no endpoint but require a
|
||||||
# hook execution to update auth strategy.
|
# hook execution to update auth strategy.
|
||||||
return
|
return
|
||||||
|
|
||||||
ensure_valid_service(settings['service'])
|
ensure_valid_service(settings['service'])
|
||||||
|
|
||||||
add_endpoint(region=settings['region'], service=settings['service'],
|
add_endpoint(region=settings['region'], service=settings['service'],
|
||||||
publicurl=settings['public_url'],
|
publicurl=settings['public_url'],
|
||||||
adminurl=settings['admin_url'],
|
adminurl=settings['admin_url'],
|
||||||
@ -250,6 +275,7 @@ def identity_changed(relation_id=None, remote_unit=None):
|
|||||||
"service_password": service_password,
|
"service_password": service_password,
|
||||||
"service_tenant": config['service-tenant']
|
"service_tenant": config['service-tenant']
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if clustered and use vip + haproxy ports if so
|
# Check if clustered and use vip + haproxy ports if so
|
||||||
if is_clustered():
|
if is_clustered():
|
||||||
relation_data["auth_host"] = config['vip']
|
relation_data["auth_host"] = config['vip']
|
||||||
@ -257,6 +283,17 @@ def identity_changed(relation_id=None, remote_unit=None):
|
|||||||
relation_data["service_host"] = config['vip']
|
relation_data["service_host"] = config['vip']
|
||||||
relation_data["service_port"] = SERVICE_PORTS['keystone_service']
|
relation_data["service_port"] = SERVICE_PORTS['keystone_service']
|
||||||
|
|
||||||
|
# generate or get a new cert/key for service if set to manage certs.
|
||||||
|
if config['https-service-endpoints'] in ['True', 'true']:
|
||||||
|
ca = get_ca()
|
||||||
|
service = os.getenv('JUJU_REMOTE_UNIT').split('/')[0]
|
||||||
|
cert, key = ca.get_cert_and_key(common_name=settings['private-address'])
|
||||||
|
ca_bundle= ca.get_ca_bundle()
|
||||||
|
relation_data['ssl_cert'] = b64encode(cert)
|
||||||
|
relation_data['ssl_key'] = b64encode(key)
|
||||||
|
relation_data['ca_cert'] = b64encode(ca_bundle)
|
||||||
|
unison.sync_to_peers(peer_interface='cluster',
|
||||||
|
paths=[SSL_DIR], user=SSH_USER, verbose=True)
|
||||||
relation_set(relation_data)
|
relation_set(relation_data)
|
||||||
synchronize_service_credentials()
|
synchronize_service_credentials()
|
||||||
|
|
||||||
@ -299,8 +336,17 @@ SERVICE_PORTS = {
|
|||||||
"keystone_service": int(config['service-port']) + 1
|
"keystone_service": int(config['service-port']) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def cluster_joined():
|
||||||
|
unison.ssh_authorized_peers(user=SSH_USER,
|
||||||
|
group='keystone',
|
||||||
|
peer_interface='cluster',
|
||||||
|
ensure_user=True)
|
||||||
|
|
||||||
def cluster_changed():
|
def cluster_changed():
|
||||||
|
unison.ssh_authorized_peers(user=SSH_USER,
|
||||||
|
group='keystone',
|
||||||
|
peer_interface='cluster',
|
||||||
|
ensure_user=True)
|
||||||
cluster_hosts = {}
|
cluster_hosts = {}
|
||||||
cluster_hosts['self'] = config['hostname']
|
cluster_hosts['self'] = config['hostname']
|
||||||
for r_id in relation_ids('cluster'):
|
for r_id in relation_ids('cluster'):
|
||||||
@ -381,6 +427,7 @@ hooks = {
|
|||||||
"identity-service-relation-joined": identity_joined,
|
"identity-service-relation-joined": identity_joined,
|
||||||
"identity-service-relation-changed": identity_changed,
|
"identity-service-relation-changed": identity_changed,
|
||||||
"config-changed": config_changed,
|
"config-changed": config_changed,
|
||||||
|
"cluster-relation-joined": cluster_joined,
|
||||||
"cluster-relation-changed": cluster_changed,
|
"cluster-relation-changed": cluster_changed,
|
||||||
"cluster-relation-departed": cluster_changed,
|
"cluster-relation-departed": cluster_changed,
|
||||||
"ha-relation-joined": ha_relation_joined,
|
"ha-relation-joined": ha_relation_joined,
|
||||||
|
@ -4,15 +4,25 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from lib.openstack_common import *
|
from lib.openstack_common import *
|
||||||
|
|
||||||
|
import keystone_ssl as ssl
|
||||||
|
import keystone_ssh as unison
|
||||||
|
|
||||||
keystone_conf = "/etc/keystone/keystone.conf"
|
keystone_conf = "/etc/keystone/keystone.conf"
|
||||||
stored_passwd = "/var/lib/keystone/keystone.passwd"
|
stored_passwd = "/var/lib/keystone/keystone.passwd"
|
||||||
stored_token = "/var/lib/keystone/keystone.token"
|
stored_token = "/var/lib/keystone/keystone.token"
|
||||||
SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
|
SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
|
||||||
|
|
||||||
|
SSL_DIR = '/var/lib/keystone/juju_ssl/'
|
||||||
|
SSL_CA_NAME = 'Ubuntu Cloud'
|
||||||
|
|
||||||
|
SSH_USER='juju_keystone'
|
||||||
|
|
||||||
def execute(cmd, die=False, echo=False):
|
def execute(cmd, die=False, echo=False):
|
||||||
""" Executes a command
|
""" Executes a command
|
||||||
|
|
||||||
@ -95,6 +105,19 @@ def relation_set_2(**kwargs):
|
|||||||
cmd += args
|
cmd += args
|
||||||
subprocess.check_call(cmd)
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def unit_get(attribute):
|
||||||
|
cmd = [
|
||||||
|
'unit-get',
|
||||||
|
attribute
|
||||||
|
]
|
||||||
|
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||||
|
if value == "":
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
def relation_get(relation_data):
|
def relation_get(relation_data):
|
||||||
""" Obtain all current relation data
|
""" Obtain all current relation data
|
||||||
relation_data is a list of options to query from the relation
|
relation_data is a list of options to query from the relation
|
||||||
@ -356,8 +379,7 @@ def ensure_initial_admin(config):
|
|||||||
create_role("KeystoneAdmin", config["admin-user"], 'admin')
|
create_role("KeystoneAdmin", config["admin-user"], 'admin')
|
||||||
create_role("KeystoneServiceAdmin", config["admin-user"], 'admin')
|
create_role("KeystoneServiceAdmin", config["admin-user"], 'admin')
|
||||||
create_service_entry("keystone", "identity", "Keystone Identity Service")
|
create_service_entry("keystone", "identity", "Keystone Identity Service")
|
||||||
# following documentation here, perhaps we should be using juju
|
|
||||||
# public/private addresses for public/internal urls.
|
|
||||||
if is_clustered():
|
if is_clustered():
|
||||||
juju_log("Creating endpoint for clustered configuration")
|
juju_log("Creating endpoint for clustered configuration")
|
||||||
for region in config['region'].split():
|
for region in config['region'].split():
|
||||||
@ -543,17 +565,32 @@ def synchronize_service_credentials():
|
|||||||
Broadcast service credentials to peers or consume those that have been
|
Broadcast service credentials to peers or consume those that have been
|
||||||
broadcasted by peer, depending on hook context.
|
broadcasted by peer, depending on hook context.
|
||||||
'''
|
'''
|
||||||
if os.path.basename(sys.argv[0]) == 'cluster-relation-changed':
|
if (not eligible_leader() or
|
||||||
r_data = relation_get_dict()
|
not os.path.isfile(SERVICE_PASSWD_PATH)):
|
||||||
if 'service_credentials' in r_data:
|
|
||||||
juju_log('Saving service passwords from peer.')
|
|
||||||
save_stored_passwords(**json.loads(r_data['service_credentials']))
|
|
||||||
return
|
|
||||||
|
|
||||||
creds = load_stored_passwords()
|
|
||||||
if not creds:
|
|
||||||
return
|
return
|
||||||
juju_log('Synchronizing service passwords to all peers.')
|
juju_log('Synchronizing service passwords to all peers.')
|
||||||
creds = json.dumps(creds)
|
unison.sync_to_peers(peer_interface='cluster',
|
||||||
for r_id in (relation_ids('cluster') or []):
|
paths=[SERVICE_PASSWD_PATH], user=SSH_USER,
|
||||||
relation_set_2(rid=r_id, service_credentials=creds)
|
verbose=True)
|
||||||
|
|
||||||
|
CA = []
|
||||||
|
def get_ca(user='keystone', group='keystone'):
|
||||||
|
"""
|
||||||
|
Initialize a new CA object if one hasn't already been loaded.
|
||||||
|
This will create a new CA or load an existing one.
|
||||||
|
"""
|
||||||
|
if not CA:
|
||||||
|
if not os.path.isdir(SSL_DIR):
|
||||||
|
os.mkdir(SSL_DIR)
|
||||||
|
d_name = '_'.join(SSL_CA_NAME.lower().split(' '))
|
||||||
|
ca = ssl.JujuCA(name=SSL_CA_NAME,
|
||||||
|
ca_dir=os.path.join(SSL_DIR,
|
||||||
|
'%s_intermediate_ca' % d_name),
|
||||||
|
root_ca_dir=os.path.join(SSL_DIR,
|
||||||
|
'%s_root_ca' % d_name))
|
||||||
|
# SSL_DIR is synchronized via all peers over unison+ssh, need
|
||||||
|
# to ensure permissions.
|
||||||
|
execute('chown -R %s.%s %s' % (user, group, SSL_DIR))
|
||||||
|
execute('chmod -R g+rwx %s' % SSL_DIR)
|
||||||
|
CA.append(ca)
|
||||||
|
return CA[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user