def request_server_certificates(): '''Send the data that is required to create a server certificate for this server.''' website = endpoint_from_flag('website.available') # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), get_ingress_address(website.endpoint_name), socket.gethostname(), ] hacluster = endpoint_from_flag('ha.connected') if hacluster: vips = hookenv.config('ha-cluster-vip').split() dns_record = hookenv.config('ha-cluster-dns') if vips: sans.extend(vips) else: sans.append(dns_record) # maybe they have extra names they want as SANs extra_sans = hookenv.config('extra_sans') if extra_sans and not extra_sans == "": sans.extend(extra_sans.split()) # Request a server cert with this information. tls_client.request_server_cert(common_name, sans, crt_path=server_crt_path, key_path=server_key_path)
def start_che(): # This container isn't Che. This container starts Che. This should be run # in interactive mode, but this container waits until it can reach che on # its public_ip. On a public cloud, public_ip might only be accessible # after running `juju expose`, so this might never exit. Because of this # reason, we run the container in daemon mode, check che's status ourselves # and kill the container manually after Che is up. print('Starting Che...') container_id = check_output([ 'docker', 'run', '-id', '-v', '/var/run/docker.sock:/var/run/docker.sock', '-v', '/home/ubuntu/:/data', '-e', 'CHE_HOST={}'.format(unit_public_ip()), '-e', 'CHE_DOCKER_IP_EXTERNAL={}'.format(unit_public_ip()), 'eclipse/che:{}'.format(CHE_VERSION), 'start', '--fast' ], universal_newlines=True).rstrip() wait_until_che_running() print('Che Started!') print('Stopping Startup Container...') try: sys.stdout.flush() check_call(['docker', 'stop', container_id]) except CalledProcessError: # container has already stopped print("Killing startup container failed.") print("Removing startup container...") sys.stdout.flush() check_call(['docker', 'rm', container_id]) print("Startup container removed!")
def start_che(): # This container isn't Che. This container starts Che. This should be run # in interactive mode, but this container waits until it can reach che on # its public_ip. On a public cloud, public_ip might only be accessible # after running `juju expose`, so this might never exit. Because of this # reason, we run the container in daemon mode, check che's status ourselves # and kill the container manually after Che is up. print('Starting Che...') container_id = check_output([ 'docker', 'run', '-id', '-v', '/var/run/docker.sock:/var/run/docker.sock', '-v', '/home/ubuntu/:/data', '-e', 'CHE_HOST={}'.format(unit_public_ip()), '-e', 'CHE_DOCKER_IP_EXTERNAL={}'.format(unit_public_ip()), 'eclipse/che:{}'.format(config()['version']), 'start', '--fast'], universal_newlines=True).rstrip() wait_until_che_running() print('Che Started!') print('Stopping Startup Container...') try: sys.stdout.flush() check_call(['docker', 'stop', container_id]) except CalledProcessError: # container has already stopped print("Killing startup container failed.") print("Removing startup container...") sys.stdout.flush() check_call(['docker', 'rm', container_id]) print("Startup container removed!")
def send_data(tls): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Get the SDN gateway based on the cidr address. kubernetes_service_ip = get_kubernetes_service_ip() domain = hookenv.config('dns_domain') # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), hookenv.unit_private_ip(), socket.gethostname(), kubernetes_service_ip, 'kubernetes', 'kubernetes.{0}'.format(domain), 'kubernetes.default', 'kubernetes.default.svc', 'kubernetes.default.svc.{0}'.format(domain) ] # maybe they have extra names they want as SANs extra_sans = hookenv.config('extra_sans') if extra_sans and not extra_sans == "": sans.extend(extra_sans.split()) # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def send_data(tls, kube_api_endpoint): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Get the SDN gateway based on the cidr address. kubernetes_service_ip = get_kubernetes_service_ip() # Get ingress address ingress_ip = get_ingress_address(kube_api_endpoint.relation_name) domain = hookenv.config('dns_domain') # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), ingress_ip, socket.gethostname(), kubernetes_service_ip, 'kubernetes', 'kubernetes.{0}'.format(domain), 'kubernetes.default', 'kubernetes.default.svc', 'kubernetes.default.svc.{0}'.format(domain) ] # maybe they have extra names they want as SANs extra_sans = hookenv.config('extra_sans') if extra_sans and not extra_sans == "": sans.extend(extra_sans.split()) # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def send_data(tls): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Get the SDN gateway based on the cidr address. kubernetes_service_ip = get_kubernetes_service_ip() domain = hookenv.config('dns_domain') # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), hookenv.unit_private_ip(), socket.gethostname(), kubernetes_service_ip, 'kubernetes', 'kubernetes.{0}'.format(domain), 'kubernetes.default', 'kubernetes.default.svc', 'kubernetes.default.svc.{0}'.format(domain) ] # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def send_data(): '''Send the data that is required to create a server certificate for this server.''' kube_control = endpoint_from_flag('kube-control.connected') # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() ingress_ip = get_ingress_address(kube_control.endpoint_name) # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), ingress_ip, gethostname() ] # Request a server cert with this information. layer.tls_client.request_server_cert(common_name, sorted(set(sans)), crt_path=server_crt_path, key_path=server_key_path) # Request a client cert for kubelet. layer.tls_client.request_client_cert('system:kubelet', crt_path=client_crt_path, key_path=client_key_path)
def prepare_tls_certificates(tls): status_set('maintenance', 'Requesting tls certificates.') common_name = hookenv.unit_public_ip() sans = [] sans.append(hookenv.unit_public_ip()) sans.append(hookenv.unit_private_ip()) sans.append(socket.gethostname()) certificate_name = hookenv.local_unit().replace('/', '_') tls.request_server_cert(common_name, sans, certificate_name)
def send_data(): # Send the data that is required to create a server certificate for # this server. config = hookenv.config() server_crt_path = crtPath('server') client_crt_path = crtPath('client') server_key_path = keyPath('server') client_key_path = keyPath('client') ca_crt_path = caPath() # Use the private ip of this unit as the Common Name for the certificate. if config['ssl_cert']: for certs_path in ('server_crt_path', 'client_crt_path'): with open(certs_path, "wb") as fs: os.chmod( certs_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) fs.write(base64.b64decode(config['ssl_cert'])) if config['ssl_key']: with open(certs_path, "wb") as fks: os.chmod(certs_path, stat.S_IWUSR | stat.S_IWUSR) fks.write(base64.b64decode(config['ssl_key'])) import_srv_crt_to_keystore() if config['ssl_ca']: with open(ca_crt_path, "wb") as fca: os.chmod( ca_crt_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) fca.write(base64.b64decode(config['ssl_ca'])) import_ca_crt_to_keystore() else: common_name = hookenv.unit_private_ip() common_public_name = hookenv.unit_public_ip() sans = [ common_name, common_public_name, hookenv.unit_public_ip(), socket.gethostname(), socket.getfqdn(), ] # maybe they have extra names they want as SANs extra_sans = hookenv.config('subject_alt_names') if extra_sans and not extra_sans == "": sans.extend(extra_sans.split()) # Request a server cert with this information. tls_client.request_server_cert(common_name, sans, crt_path=server_crt_path, key_path=server_key_path) # Request a client cert with this information. tls_client.request_client_cert(common_name, sans, crt_path=client_crt_path, key_path=client_key_path)
def prepare_tls_certificates(tls): common_name = hookenv.unit_public_ip() sans = set() sans.add(hookenv.unit_public_ip()) sans.update(get_ingress_addresses('db')) sans.update(get_ingress_addresses('cluster')) sans.add(socket.gethostname()) sans = sorted(sans) certificate_name = hookenv.local_unit().replace('/', '_') tls.request_server_cert(common_name, sans, certificate_name)
def stop_che(): print('Stopping Che...') sys.stdout.flush() check_call([ 'docker', 'run', '-i', '--rm', '-v', '/var/run/docker.sock:/var/run/docker.sock', '-v', '/home/ubuntu/:/data', '-e', 'CHE_HOST={}'.format(unit_public_ip()), '-e', 'CHE_DOCKER_IP_EXTERNAL={}'.format(unit_public_ip()), 'eclipse/che:{}'.format(CHE_VERSION), 'stop' ]) print('Che is stopped!')
def prepare_tls_certificates(tls): status_set('maintenance', 'Requesting tls certificates.') common_name = hookenv.unit_public_ip() sans = set() sans.add(hookenv.unit_public_ip()) sans.add(get_ingress_address('db')) sans.add(get_ingress_address('cluster')) sans.add(socket.gethostname()) sans = list(sans) certificate_name = hookenv.local_unit().replace('/', '_') tls.request_server_cert(common_name, sans, certificate_name)
def send_data(tls): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Create SANs that the tls layer will add to the server cert. sans = [hookenv.unit_public_ip(), hookenv.unit_private_ip(), gethostname()] # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def send_data(tls): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Get a list of Subject Alt Names for the certificate. sans = [] sans.append(hookenv.unit_public_ip()) sans.append(hookenv.unit_private_ip()) sans.append(socket.gethostname()) layer.tls_client.request_server_cert(common_name, sans, crt_path='/etc/certs/server.crt', key_path='/etc/certs/server.key')
def stop_che(): print('Stopping Che...') sys.stdout.flush() check_call([ 'docker', 'run', '-i', '--rm', '-v', '/var/run/docker.sock:/var/run/docker.sock', '-v', '/home/ubuntu/:/data', '-e', 'CHE_HOST={}'.format(unit_public_ip()), '-e', 'CHE_DOCKER_IP_EXTERNAL={}'.format(unit_public_ip()), 'eclipse/che:{}'.format(config()['version']), 'stop']) print('Che is stopped!')
def request_server_certificates(tls): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), hookenv.unit_private_ip(), socket.gethostname(), ] # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def configure(): zookeeper = relations.endpoint_from_flag('endpoint.zookeeper.available') connections = [] try: connections_yaml = hookenv.config().get('connections') if connections_yaml: connections = yaml.safe_load(connections_yaml) except yaml.YAMLError: pass mysql = relations.endpoint_from_flag('shared-db.available') conf = { 'zk_servers': [], 'connections': connections, 'database': mysql, 'git_username': hookenv.config().get('git_username'), 'git_email': hookenv.config().get('git_email'), 'executor_disk_limit': hookenv.config().get('executor_disk_limit', '-1'), 'public_ip': hookenv.unit_public_ip(), } if hookenv.config()['tenant-config']: conf['tenant_config_script'] = True for zk_unit in zookeeper.list_unit_data(): conf['zk_servers'].append("{}:{}".format( zk_unit['host'].replace('"', ''), zk_unit['port'])) templating.render('zuul.conf', '/etc/zuul/zuul.conf', context=conf, perms=0o650, group='zuul', owner='zuul') if reactive.helpers.any_file_changed(['/etc/zuul/zuul.conf']): reactive.set_flag('service.zuul.restart') reactive.set_flag('zuul.reload_config') reactive.set_flag('zuul.configured')
def setup_mattermost_backend(postgres_relation): print("Configuring and starting backend service.") _configure_mattermost_postgres(postgres_relation.master.uri) service_restart('mattermost') # Set build number for Juju status try: output = check_output( ['/opt/mattermost/bin/platform', 'version'], cwd='/opt/mattermost/bin/', universal_newlines=True, stderr=STDOUT, ) except CalledProcessError as e: print(e.output) raise build_number = re.search(r'Build Number: ([0-9]+.[0-9]+.[0-9])+\n', output).group(1) application_version_set(build_number) open_port(8065) # The next two aren't really open. This is a fix for the following issue: # no expose possible before `open-port`. # no `open-port` of 80 and 443 before ssl. # no ssl certificate before `expose`. open_port(config().get('port')) open_port(443) status_set( 'active', 'Ready (http://{}:8065 [Insecure! Please set fqdn!])'.format( unit_public_ip())) set_state('mattermost.backend.started')
def config_file_changed(): """ When /etc/jupyterhub/jupyterhub_config.py changes, restart jupyterhub. """ subprocess.check_call(['systemctl', 'restart', 'jupyterhub']) hookenv.status_set('active', 'Ready: http://{public_ip}:{port}'.format( public_ip=hookenv.unit_public_ip(), port=hookenv.config('port')))
def change_config(): conf = config() if conf.changed('port') or conf.changed('authentication'): old_port = conf.previous('port') render(source='arangod.conf', target='/etc/arangodb3/arangod.conf', context={ 'port': str(conf['port']), 'authentication': str(conf['authentication']).lower() }) if old_port is not None: close_port(old_port) open_port(conf['port']) if conf['root_password'] != kv.get( 'password') and conf['root_password'] != "": password = conf['root_password'] old_password = kv.get('password') kv.set('password', password) TCP = 'tcp://' + unit_public_ip() + ':' + str(conf['port']) require = "require('@arangodb/users').update('root', '{}', true)".format( password) subprocess.check_call([ 'arangosh', '--server.endpoint', TCP, '--server.username', 'root', '--server.password', old_password, '--javascript.execute-string', require ])
def config_with_reverseproxy(reverseproxy): services = reverseproxy.services() cfg = hookenv.config() for service in services: service_dir = '/var/lib/tor/%s' % (service['service_name']) if not os.path.isdir(service_dir): check_call(['install', '-d', service_dir, '-o', 'debian-tor', '-m', '700']) bridges = [] for bridge in cfg.get('bridges', '').split(','): fields = bridge.split() if len(fields) > 1: addr, fp = fields[:2] bridges.append({'addr': addr, 'fingerprint': fp}) render(source='torrc', target='/etc/tor/torrc', owner='root', perms=0o644, context={ 'cfg': cfg, 'services': services, 'bridges': bridges, 'public_address': hookenv.unit_public_ip(), 'private_address': hookenv.unit_private_ip(), }) remove_state('reverseproxy.available') set_state('tor.start')
def create_certificate_authority(certificate_authority=None): '''Return the CA and server certificates for this system. If the CA is empty, generate a self signged certificate authority.''' # followers are not special, do not generate a ca if not is_leader(): return # Create an absolute path so current directory does not affect the result. easyrsa3_dir = os.path.join(hookenv.charm_dir(), 'easy-rsa/easyrsa3') with chdir(easyrsa3_dir): ca_file = 'pki/ca.crt' # Check if an old CA exists. if os.path.isfile(ca_file): # Initialize easy-rsa (by deleting old pki) so a CA can be created. init = './easyrsa --batch init-pki 2>&1' check_call(split(init)) # When the CA is not None write the CA file. if certificate_authority: # Write the certificate authority from configuration. with open(ca_file, 'w') as fp: fp.write(certificate_authority) else: # The Certificate Authority does not exist build a self signed one. # The Common Name (CN) for a certificate must be an IP or hostname. cn = hookenv.unit_public_ip() # Create a self signed CA with the CN, stored pki/ca.crt build_ca = './easyrsa --batch "--req-cn={0}" build-ca nopass 2>&1' check_call(split(build_ca.format(cn))) # Read the CA so we can return the contents from this method. with open(ca_file, 'r') as fp: certificate_authority = fp.read() set_state('certificate authority available') return certificate_authority
def get_sans(address_list=None): '''Return a string suitable for the easy-rsa subjectAltNames. This method will add a valid SANs string with the public IP, private IP, and hostname of THIS system.''' # The unit_public_ip could be a FQDN or IP address depending on provider. public = hookenv.unit_public_ip() # unitdata returns None if not found. Handle the occurrence of no # addresses passed, and initialize to an empty array if not address_list: address_list = [] if public not in address_list: address_list.append(public) # The unit_private_ip could be a FQDN or IP address depending on provider. private = hookenv.unit_private_ip() if private not in address_list: address_list.append(private) # The hostname is usually a string, not an IP address. hostname = socket.gethostname() if hostname not in address_list: address_list.append(hostname) sans = [] for address in address_list: if _is_ip(address): sans.append('IP:{0}'.format(address)) else: sans.append('DNS:{0}'.format(address)) return ','.join(sans)
def get_sans(address_list=None): """Return a string suitable for the easy-rsa subjectAltNames. This method will add a valid SANs string with the public IP, private IP, and hostname of THIS system.""" # The unit_public_ip could be a FQDN or IP address depending on provider. public = hookenv.unit_public_ip() # unitdata returns None if not found. Handle the occurrence of no # addresses passed, and initialize to an empty array if not address_list: address_list = [] if public not in address_list: address_list.append(public) # The unit_private_ip could be a FQDN or IP address depending on provider. private = hookenv.unit_private_ip() if private not in address_list: address_list.append(private) # The hostname is usually a string, not an IP address. hostname = socket.gethostname() if hostname not in address_list: address_list.append(hostname) sans = [] for address in address_list: if _is_ip(address): sans.append("IP:{0}".format(address)) else: sans.append("DNS:{0}".format(address)) return ",".join(sans)
def create_server_certificate(name="server"): """Create the server certificate and server key.""" # Use the public ip as the Common Name for the server certificate. cn = hookenv.unit_public_ip() # Create an absolute path so current directory does not affect the result. easyrsa3_dir = os.path.join(hookenv.charm_dir(), "easy-rsa/easyrsa3") with chdir(easyrsa3_dir): server_file = "pki/issued/{0}.crt".format(name) # Get a list of extra sans from the unitdata kv module. extra_sans = unitdata.kv().get("extra_sans") # Get a string compatible with easyrsa for the subject-alt-names. sans = get_sans(extra_sans) # Do not regenerate the server certificate if it already exists. if not os.path.isfile(server_file): # Create a server certificate for the server based on the CN. server = ( "./easyrsa --batch --req-cn={0} --subject-alt-name={1} " "build-server-full {2} nopass 2>&1".format(cn, sans, name) ) check_call(split(server)) # Read the server certificate from the filesystem. with open(server_file, "r") as fp: server_cert = fp.read() # The key name is also used to set the reactive state. set_cert("tls.server.certificate", server_cert)
def create_certificate_authority(certificate_authority=None): """Return the CA and server certificates for this system. If the CA is empty, generate a self signged certificate authority.""" # followers are not special, do not generate a ca if not is_leader(): return # Create an absolute path so current directory does not affect the result. easyrsa3_dir = os.path.join(hookenv.charm_dir(), "easy-rsa/easyrsa3") with chdir(easyrsa3_dir): ca_file = "pki/ca.crt" # Check if an old CA exists. if os.path.isfile(ca_file): # Initialize easy-rsa (by deleting old pki) so a CA can be created. init = "./easyrsa --batch init-pki 2>&1" check_call(split(init)) # When the CA is not None write the CA file. if certificate_authority: # Write the certificate authority from configuration. with open(ca_file, "w") as fp: fp.write(certificate_authority) else: # The Certificate Authority does not exist build a self signed one. # The Common Name (CN) for a certificate must be an IP or hostname. cn = hookenv.unit_public_ip() # Create a self signed CA with the CN, stored pki/ca.crt build_ca = './easyrsa --batch "--req-cn={0}" build-ca nopass 2>&1' check_call(split(build_ca.format(cn))) # Read the CA so we can return the contents from this method. with open(ca_file, "r") as fp: certificate_authority = fp.read() set_state("certificate authority available") return certificate_authority
def create_csr(tls): """Create a certificate signing request (CSR). Only the followers need to run this operation.""" if not is_leader(): # Create an absolute path to easyrsa3 to change to that directory. easyrsa3_dir = os.path.join(hookenv.charm_dir(), "easy-rsa/easyrsa3") # Use an absolute path for this context manager. with chdir(easyrsa3_dir): # Must remove the path characters from the unit name. path_name = hookenv.local_unit().replace("/", "_") # The reqest will be named with unit_name.req req_file = "pki/reqs/{0}.req".format(path_name) # If the request already exists do not generate another one. if os.path.isfile(req_file): remove_state("create certificate signing request") return # The Common Name is the public address of the system. cn = hookenv.unit_public_ip() hookenv.log("Creating the CSR for {0}".format(path_name)) sans = get_sans() # Create a CSR for this system with the subject and SANs. gen_req = "./easyrsa --batch --req-cn={0} --subject-alt-name={1}" " gen-req {2} nopass 2>&1".format( cn, sans, path_name ) check_call(split(gen_req)) # Read the CSR file. with open(req_file, "r") as fp: csr = fp.read() # Set the CSR on the relation object. tls.set_csr(csr) else: hookenv.log("The leader does not need to create a CSR.")
def get_template_data(): rels = hookenv.relations() config = hookenv.config() version = config['version'] template_data = {} template_data['etcd_servers'] = ','.join([ 'http://%s:%s' % (s[0], s[1]) for s in sorted( get_rel_hosts('etcd', rels, ('hostname', 'port')))]) template_data['minions'] = ','.join(get_rel_hosts('minions-api', rels)) private_ip = hookenv.unit_private_ip() public_ip = hookenv.unit_public_ip() template_data['api_public_address'] = _bind_addr(public_ip) template_data['api_private_address'] = _bind_addr(private_ip) template_data['bind_address'] = '127.0.0.1' template_data['api_http_uri'] = 'http://%s:%s' % (private_ip, 8080) template_data['api_https_uri'] = 'https://%s:%s' % (private_ip, 6443) arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() template_data['web_uri'] = '/kubernetes/%s/local/bin/linux/%s/' % (version, arch) if version == 'local': template_data['alias'] = hookenv.charm_dir() + '/files/output/' else: directory = '/opt/kubernetes/_output/local/bin/linux/%s/' % arch template_data['alias'] = directory _encode(template_data) return template_data
def master_kubeconfig(): '''Create the kubernetes configuration for the master unit. The master should create a package with the client credentials so the user can interact securely with the apiserver.''' hookenv.log('Creating Kubernetes configuration for master node.') directory = '/srv/kubernetes' ca = '/srv/kubernetes/ca.crt' key = '/srv/kubernetes/client.key' cert = '/srv/kubernetes/client.crt' # Get the public address of the apiserver so users can access the master. server = 'https://{0}:{1}'.format(hookenv.unit_public_ip(), '6443') # Create the client kubeconfig so users can access the master node. create_kubeconfig(directory, server, ca, key, cert) # Copy the kubectl binary to this directory. cmd = 'cp -v /usr/local/bin/kubectl {0}'.format(directory) check_call(split(cmd)) # Use a context manager to run the tar command in a specific directory. with chdir(directory): # Create a package with kubectl and the files to use it externally. cmd = 'tar -cvzf /home/ubuntu/kubectl_package.tar.gz ca.crt ' \ 'client.key client.crt kubectl kubeconfig' check_call(split(cmd)) # This sets up the client workspace consistently on the leader and nodes. node_kubeconfig() set_state('kubeconfig.created')
def render_systemd_conf(): """Render fiche systemd conf """ if config('fqdn'): server_name = config('fqdn') else: server_name = unit_public_ip() # Systemd vars SYSTEMD_CTXT = { 'fiche_server_address': server_name, 'fiche_server_port': config('fiche-server-port'), 'slug_size': config('slug-size'), 'buffer_size': config('buffer-size') } if os.path.exists('/etc/systemd/system/fiche.service'): os.remove('/etc/systemd/system/fiche.service') # Render systemd template render(source="fiche.service.tmpl", target="/etc/systemd/system/fiche.service", perms=0o644, owner="root", context=SYSTEMD_CTXT) # Open fiche server port open_port(config('fiche-server-port')) # Set 'fiche.systemd.configured' set_state('fiche.systemd.configured')
def config_leader(): leader_set(hostname=hookenv.unit_private_ip()) leader_set(public_ip=hookenv.unit_public_ip()) leader_set(username='******') leader_set(password=hookenv.config('carte_password')) leader_set(port=hookenv.config('carte_port')) render_master_config()
def setup(): unit_data = kv() if not unit_data.get('gogs.db'): hookenv.status_set('blocked', 'need relation to postgresql') return secret_key = unit_data.get('gogs.secret_key') if not secret_key: secret_key = base64.b64encode(os.urandom(32)).decode('utf-8') unit_data.set('gogs.secret_key', secret_key) conf = hookenv.config() if not conf.get('host'): conf['host'] = hookenv.unit_public_ip() root = unit_data.get('gogs.root', '') if root and not root.endswith('/'): root = root + '/' install_context = get_install_context() render(source='app.ini', target="/opt/gogs/custom/conf/app.ini", perms=0o644, context={ 'conf': conf, 'db': unit_data.get('gogs.db'), 'secret_key': secret_key, 'root': root, 'home': install_context['home'], 'user': install_context['user'], }) restart_service() hookenv.status_set('active', 'ready')
def create_csr(tls): '''Create a certificate signing request (CSR). Only the followers need to run this operation.''' if not is_leader(): # Create an absolute path to easyrsa3 to change to that directory. easyrsa3_dir = os.path.join(hookenv.charm_dir(), 'easy-rsa/easyrsa3') # Use an absolute path for this context manager. with chdir(easyrsa3_dir): # Must remove the path characters from the unit name. path_name = hookenv.local_unit().replace('/', '_') # The reqest will be named with unit_name.req req_file = 'pki/reqs/{0}.req'.format(path_name) # If the request already exists do not generate another one. if os.path.isfile(req_file): remove_state('create certificate signing request') return # The Common Name is the public address of the system. cn = hookenv.unit_public_ip() hookenv.log('Creating the CSR for {0}'.format(path_name)) sans = get_sans() # Create a CSR for this system with the subject and SANs. gen_req = './easyrsa --batch --req-cn={0} --subject-alt-name={1}' \ ' gen-req {2} nopass 2>&1'.format(cn, sans, path_name) check_call(split(gen_req)) # Read the CSR file. with open(req_file, 'r') as fp: csr = fp.read() # Set the CSR on the relation object. tls.set_csr(csr) else: hookenv.log('The leader does not need to create a CSR.')
def config_with_reverseproxy(reverseproxy): services = reverseproxy.services() cfg = hookenv.config() for service in services: service_dir = '/var/lib/tor/{}'.format(service['service_name']) if not os.path.isdir(service_dir): check_call([ 'install', '-d', service_dir, '-o', 'debian-tor', '-m', '700' ]) bridges = [] for bridge in cfg.get('bridges', '').split(','): fields = bridge.split() if len(fields) > 1: addr, fp = fields[:2] bridges.append({'addr': addr, 'fingerprint': fp}) render(source='torrc', target='/etc/tor/torrc', owner='root', perms=0o644, context={ 'cfg': cfg, 'services': services, 'bridges': bridges, 'public_address': hookenv.unit_public_ip(), 'private_address': hookenv.unit_private_ip(), }) remove_state('reverseproxy.available') set_state('tor.start')
def create_certificate_authority(): '''Return the CA and server certificates for this system. If the CA is empty, generate a self signged certificate authority.''' with chdir(easyrsa_directory): # The Common Name (CN) for a certificate must be an IP or hostname. cn = hookenv.unit_public_ip() # Create a self signed CA with the CN, stored pki/ca.crt build_ca = './easyrsa --batch "--req-cn={0}" build-ca nopass 2>&1' # Build a self signed Certificate Authority. check_call(split(build_ca.format(cn))) ca_file = 'pki/ca.crt' # Read the CA so it can be returned in leader data. with open(ca_file, 'r') as stream: certificate_authority = stream.read() key_file = 'pki/private/ca.key' # Read the private key so it can be set in leader data. with open(key_file, 'r') as stream: ca_key = stream.read() # Set these values on the leadership data. leader_set({'certificate_authority': certificate_authority}) leader_set({'certificate_authority_key': ca_key}) # Install the CA on this system as a trusted CA. install_ca(certificate_authority) # Create a client certificate for this CA. client_cert, client_key = create_client_certificate() # Set the client certificate and key on leadership data. leader_set({'client_certificate': client_cert}) leader_set({'client_key': client_key}) status_set('active', 'Certificiate Authority available') set_state('easyrsa.certificate.authority.available')
def get_tls_sans(relation_name=None): '''Get all sans for our TLS certificate. Return all IP/DNS data that should included as alt names when we request a TLS cert. This includes our public/private address, local DNS name, any configured hostname, and the address of any related proxy. :return: sorted list of sans ''' charm_config = hookenv.config() sans = [ hookenv.unit_private_ip(), hookenv.unit_public_ip(), socket.gethostname(), ] if charm_config.get('http-host'): http_host = urlparse(charm_config['http-host']).hostname sans.append(http_host) if relation_name: proxy_sans = [hookenv.ingress_address(rid=u.rid, unit=u.unit) for u in hookenv.iter_units_for_relation_name(relation_name)] sans.extend(proxy_sans) return sorted(sans)
def setup(): unit_data = kv() if not unit_data.get('gogs.db'): hookenv.status_set('blocked', 'need relation to postgresql') return secret_key = unit_data.get('gogs.secret_key') if not secret_key: secret_key = base64.b64encode(os.urandom(32)).decode('utf-8') unit_data.set('gogs.secret_key', secret_key) conf = hookenv.config() if not conf.get('host'): conf['host'] = hookenv.unit_public_ip() root = unit_data.get('gogs.root', '') if root and not root.endswith('/'): root = root + '/' render(source='app.ini', target="/opt/gogs/custom/conf/app.ini", perms=0o644, context={ 'conf': conf, 'db': unit_data.get('gogs.db'), 'secret_key': secret_key, 'root': root, }) restart_service() hookenv.status_set('active', 'ready')
def create_certificate_authority(certificate_authority=None): '''Return the CA and server certificates for this system. If the CA is empty, generate a self signged certificate authority.''' # followers are not special, do not generate a ca if not is_leader(): return with chdir('easy-rsa/easyrsa3'): ca_file = 'pki/ca.crt' # Check if an old CA exists. if os.path.isfile(ca_file): # Initialize easy-rsa (by deleting old pki) so a CA can be created. init = './easyrsa --batch init-pki 2>&1' check_call(split(init)) # When the CA is not None write the CA file. if certificate_authority: # Write the certificate authority from configuration. with open(ca_file, 'w') as fp: fp.write(certificate_authority) else: # The Certificate Authority does not exist build a self signed one. # The Common Name (CN) for a certificate must be an IP or hostname. cn = hookenv.unit_public_ip() # Create a self signed CA with the CN, stored pki/ca.crt build_ca = './easyrsa --batch "--req-cn={0}" build-ca nopass 2>&1' check_call(split(build_ca.format(cn))) # Read the CA so we can return the contents from this method. with open(ca_file, 'r') as fp: certificate_authority = fp.read() set_state('certificate authority available') return certificate_authority
def get_template_data(): rels = hookenv.relations() config = hookenv.config() version = config['version'] template_data = {} template_data['etcd_servers'] = ','.join([ 'http://%s:%s' % (s[0], s[1]) for s in sorted(get_rel_hosts('etcd', rels, ('hostname', 'port'))) ]) template_data['minions'] = ','.join(get_rel_hosts('minions-api', rels)) private_ip = hookenv.unit_private_ip() public_ip = hookenv.unit_public_ip() template_data['api_public_address'] = _bind_addr(public_ip) template_data['api_private_address'] = _bind_addr(private_ip) template_data['bind_address'] = '127.0.0.1' template_data['api_http_uri'] = 'http://%s:%s' % (private_ip, 8080) template_data['api_https_uri'] = 'https://%s:%s' % (private_ip, 6443) arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() template_data['web_uri'] = '/kubernetes/%s/local/bin/linux/%s/' % (version, arch) if version == 'local': template_data['alias'] = hookenv.charm_dir() + '/files/output/' else: directory = '/opt/kubernetes/_output/local/bin/linux/%s/' % arch template_data['alias'] = directory _encode(template_data) return template_data
def setup_mattermost_backend(postgres_relation): print("Configuring and starting backend service.") _configure_mattermost_postgres(postgres_relation.master.uri) service_restart('mattermost') # Set build number for Juju status try: output = check_output( ['/opt/mattermost/bin/platform', 'version'], cwd='/opt/mattermost/bin/', universal_newlines=True, stderr=STDOUT, ) except CalledProcessError as e: print(e.output) raise build_number = re.search(r'Build Number: ([0-9]+.[0-9]+.[0-9])+\n', output).group(1) application_version_set(build_number) open_port(8065) # The next two aren't really open. This is a fix for the following issue: # no expose possible before `open-port`. # no `open-port` of 80 and 443 before ssl. # no ssl certificate before `expose`. open_port(config().get('port')) open_port(443) status_set( 'active', 'Ready (http://{}:8065 [Insecure! Please set fqdn!])'.format(unit_public_ip())) set_state('mattermost.backend.started')
def init_nextcloud(mysql): status_set('maintenance', "Initializing Nextcloud") ctxt = {'dbname': mysql.database(), 'dbuser': mysql.user(), 'dbpass': mysql.password(), 'dbhost': mysql.host(), 'dbport': mysql.port(), 'dbtype': 'mysql', 'admin_username': config().get('admin-username'), 'admin_password': config().get('admin-password'), 'data_dir': Path('/var/www/nextcloud/data'), } nextcloud_init = ("sudo -u www-data /usr/bin/php occ maintenance:install " "--database {dbtype} --database-name {dbname} " "--database-host {dbhost} --database-pass {dbpass} " "--database-user {dbuser} --admin-user {admin_username} " "--admin-pass {admin_password} " "--data-dir {data_dir} ").format(**ctxt) log(nextcloud_init) with chdir('/var/www/nextcloud'): subprocess.call("sudo chown -R www-data:www-data .".split()) subprocess.call(nextcloud_init.split()) Path('/var/www/nextcloud/config/config.php').write_text( Path('/var/www/nextcloud/config/config.php').open().read().replace( "localhost", config().get('fqdn') or unit_public_ip())) set_flag('nextcloud.initdone') status_set('active', "Nextcloud init complete")
def create_csr(tls): '''Create a certificate signing request (CSR). Only the followers need to run this operation.''' if not is_leader(): with chdir('easy-rsa/easyrsa3'): # Must remove the path characters from the unit name. path_name = hookenv.local_unit().replace('/', '_') # The reqest will be named with unit_name.req req_file = 'pki/reqs/{0}.req'.format(path_name) # If the request already exists do not generate another one. if os.path.isfile(req_file): remove_state('create certificate signing request') return # The Common Name is the public address of the system. cn = hookenv.unit_public_ip() hookenv.log('Creating the CSR for {0}'.format(path_name)) sans = get_sans() # Create a CSR for this system with the subject and SANs. gen_req = './easyrsa --batch --req-cn={0} --subject-alt-name={1}' \ ' gen-req {2} nopass 2>&1'.format(cn, sans, path_name) check_call(split(gen_req)) # Read the CSR file. with open(req_file, 'r') as fp: csr = fp.read() # Set the CSR on the relation object. tls.set_csr(csr) else: hookenv.log('The leader does not need to create a CSR.')
def configure(force=False): config = hookenv.config() def changed(key): return force or config.changed(key) if config.changed('proxy') and config.get('proxy'): shutil.rmtree('/opt/collector-web') install() if hookenv.status_get() == 'blocked': return # We're blocked again with open('/etc/graphite/local_settings.py', 'r+') as f: contents = f.read() contents = re.sub(r'#TIME_ZONE = .*', "TIME_ZONE = 'Etc/UTC'", contents) f.seek(0, 0) f.truncate() f.write(contents) if 'juju-secret' not in config: return ini_path = '/opt/collector-web/production.ini' with open(ini_path, 'r') as f: ini = f.read() api_addresses = os.getenv('JUJU_API_ADDRESSES') if api_addresses: juju_api = 'wss://%s' % api_addresses.split()[0] ini = re.sub(r'juju.api.endpoint =.*', 'juju.api.endpoint = %s' % juju_api, ini) ini = re.sub( r'graphite.url =.*', 'graphite.url = http://%s:9001' % hookenv.unit_get('public-address'), ini) if changed('juju-user'): ini = re.sub( r'juju.api.user =.*', 'juju.api.user = %s' % config.get('juju-user') or '', ini) if changed('juju-secret'): ini = re.sub( r'juju.api.secret =.*', 'juju.api.secret = %s' % config.get('juju-secret') or '', ini) if changed('publish-url'): ini = re.sub( r'publish.url =.*', 'publish.url = %s' % config.get('publish-url') or '', ini) with open(ini_path, 'w') as f: f.write(ini) host.service_restart('collectorweb') hookenv.status_set('active', 'Ready http://%s:9000' % hookenv.unit_public_ip())
def fiche_available(): """Start or restart fiche, set status and state """ start_restart() # Set status status_set('active', 'Fiche is active at: %s' % ("%s:%s" % (unit_public_ip(), config('fiche-server-port')))) set_state('fiche.available')
def configure_interface(saltinfo): config = hookenv.config() if config['use-dns']: address = socket.gethostname() else: address = unit_public_ip() port = None saltinfo.configure(address, port)
def prepare_tls_certificates(tls): common_name = hookenv.unit_public_ip() sans = set() sans.add(hookenv.unit_public_ip()) sans.update(get_ingress_addresses('db')) sans.update(get_ingress_addresses('cluster')) sans.add(socket.gethostname()) # add cluster peers as alt names when present cluster = endpoint_from_flag('cluster.joined') if cluster: for ip in cluster.get_db_ingress_addresses(): sans.add(ip) sans = sorted(sans) certificate_name = hookenv.local_unit().replace('/', '_') tls.request_server_cert(common_name, sans, certificate_name)
def get_config_action(self): """Retrieve and return settings and key data for get-config action.""" public_key = self.kv.get("public-key") port = (self.charm_config["listen-port"], ) ip = hookenv.unit_public_ip() log("Returning configuration for action: {}, {}, {}".format( public_key, port, ip)) return public_key, ip, port
def request_server_certificates(tls, website): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), get_ingress_address(website), socket.gethostname(), ] # maybe they have extra names they want as SANs extra_sans = hookenv.config('extra_sans') if extra_sans and not extra_sans == "": sans.extend(extra_sans.split()) # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def send_data(tls, kube_control): '''Send the data that is required to create a server certificate for this server.''' # Use the public ip of this unit as the Common Name for the certificate. common_name = hookenv.unit_public_ip() ingress_ip = get_ingress_address(kube_control) # Create SANs that the tls layer will add to the server cert. sans = [ hookenv.unit_public_ip(), ingress_ip, gethostname() ] # Create a path safe name by removing path characters from the unit name. certificate_name = hookenv.local_unit().replace('/', '_') # Request a server cert with this information. tls.request_server_cert(common_name, sans, certificate_name)
def update_torrc(): cfg = hookenv.config() render( source="torrc", target="/etc/tor/torrc", owner="root", perms=0o644, context={"cfg": cfg, "public_address": hookenv.unit_public_ip(), "private_address": hookenv.unit_private_ip()}, ) remove_state("tor.configured") set_state("tor.start")
def setup(): hookenv.status_set('maintenance', 'setting up mailinabox') password = "******" # TODO: generate a decent password config = hookenv.config() setup_env = {} setup_env.update(os.environ) setup_env.update({ 'NONINTERACTIVE': '1', 'DISABLE_FIREWALL': '1', # Juju manages this 'SKIP_NETWORK_CHECKS': '1', 'PUBLIC_IP': hookenv.unit_public_ip(), 'PUBLIC_IPV6': 'auto', 'PRIMARY_HOSTNAME': config['hostname'], 'EMAIL_ADDR': 'me@%s' % (config['hostname']), 'EMAIL_PW': password, }) hookenv.open_port(53, protocol='UDP') for port in (25, 80, 443, 587, 993, 4190): hookenv.open_port(port) check_call(['setup/start.sh'], shell=True, cwd=SRC_DIR, env=setup_env) set_state('mailinabox.setup') hookenv.status_set('active', 'Admin console: https://%s/admin' % (hookenv.unit_public_ip()))
def install_site(): if config('deploy_key') is not None: render( source='key', target='/root/.ssh/id_rsa', perms=0o600, context={ 'key': config('deploy_key') } ) clone() if config('commit') is not None: update_to_commit() chown(config('app-path'), 'puma') domain = config('domain') if domain is None or domain == '': domain = unit_public_ip() render(source='puma.conf', target='/etc/init/puma.conf', owner='root', group='root', perms=0o644, context={ 'workers': config('web_workers'), 'threads': config('worker_threads'), 'app_path': config('app-path'), 'secret_key_base': config('secret_key_base'), 'domain': domain, }) render(source='nginx.conf', target='/etc/nginx/sites-enabled/puma.conf', owner='root', group='root', perms=0o644, context={ 'port': config('web_port'), 'app_path': config('app-path'), }) open_port(config('web_port')) if service_running('nginx'): service_restart('nginx') else: service_start('nginx') chown(config('app-path'), 'puma') status_set('maintenance', '') set_state('app.installed')
def rpc_broadcast_ip_address(): c = config() # Pre-juju2, rpc_interface was used to control the interface used for # client communication. if c['rpc_interface'] or not hookenv.has_juju_version('2.3'): return (interface_to_ip(c['rpc_interface']) or hookenv.unit_public_ip()) # Post-juju2, addresses for relation endpoints can be specified at deploy time # in a standard way, so listen_interface in config should be unnecessary. # Only the database endpoint is used, as Cassandra only supports a single # IP address to broadcast as the connection address. ip = hookenv.network_get('database')['ingress-addresses'][0] hookenv.log('rpc_broadcast_ip_address == {!r}'.format(ip), DEBUG) return ip
def start(*args): cmd = "/home/{}/bin/service-openmano start".format(USER) out, err = _run(cmd) if not kvdb.get('openmano-tenant'): out, err = _run('./scripts/create-tenant.sh') kvdb.set('openmano-tenant', out.strip()) status_set( 'active', 'Up on {host}:{port}'.format( host=hookenv.unit_public_ip(), port='9090')) set_state('openmano.running')
def install_openvim_controller(mysql): create_openvim_user() download_openvim() add_openvim_to_path() configure_openvim(mysql) initialize_openvim_database(mysql) generate_ssh_key() install_openvim_service() start_openvim() create_sane_defaults() status_set( 'active', 'Up on {host}:{port}'.format( host=unit_public_ip(), port='9080')) set_state('openvim-controller.installed')
def db_relation_joined(): """ Hook to run when somebody connects to us. """ passwords = json.loads(leader_get('passwords')) # TODO: Exception handling. relation_set( host=config('couchdb-host'), ip=unit_public_ip(), port=config('couchdb-port'), admin_pass=passwords['admin_pass'], repl_pass=passwords['repl_pass'] ) log("{} joined".format(os.getenv('ENSEMBLE_REMOTE_UNIT')), INFO)
def _publish_database_relation(relid, superuser): # The Casandra service needs to provide a common set of credentials # to a client unit. The leader creates these, if none of the other # units are found to have published them already (a previously elected # leader may have done this). The leader then tickles the other units, # firing a hook and giving them the opportunity to copy and publish # these credentials. username, password = _client_credentials(relid) if username is None: if hookenv.is_leader(): # Credentials not set. The leader must generate them. We use # the service name so that database permissions remain valid # even after the relation is dropped and recreated, or the # juju environment rebuild and the database restored from # backups. username = '******'.format(helpers.get_service_name(relid)) if superuser: username += '_admin' password = host.pwgen() pwhash = helpers.encrypt_password(password) with helpers.connect() as session: helpers.ensure_user(session, username, pwhash, superuser) # Wake the peers, if any. helpers.leader_ping() else: return # No credentials yet. Nothing to do. # Publish the information the client needs on the relation where # they can find it. # - authentication credentials # - address and port # - cluster_name, so clients can differentiate multiple clusters # - datacenter + rack, so clients know what names they can use # when altering keyspace replication settings. config = hookenv.config() hookenv.relation_set(relid, username=username, password=password, host=hookenv.unit_public_ip(), native_transport_port=config['native_transport_port'], rpc_port=config['rpc_port'], cluster_name=config['cluster_name'], datacenter=config['datacenter'], rack=config['rack'])