From d7301268fa71e661bd842eae97f74a4f89e55bb9 Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Robles Date: Fri, 9 Dec 2016 16:05:12 +0200 Subject: [PATCH] Add FreeIPA enrollment environment generator This is based on previous work [1] and it's what I've been using to test the TLS-everywhere work. This is a convenience tool that will generate an environment file that sets the override in the resource registry as well as the relevant parameters that will be needed in a TLS-everywhere deployment. [1] https://github.com/JAORMX/freeipa-tripleo-incubator bp tls-via-certmonger Change-Id: If3e6cb1dde235b24a0c188eb758f9a2e6a626f69 Depends-On: Iac94b3b047dca1bcabd464ea8eed6f1220c844f1 --- scripts/create_freeipa_enroll_envfile.py | 240 +++++++++++++++++++++++ setup.cfg | 1 + 2 files changed, 241 insertions(+) create mode 100755 scripts/create_freeipa_enroll_envfile.py diff --git a/scripts/create_freeipa_enroll_envfile.py b/scripts/create_freeipa_enroll_envfile.py new file mode 100755 index 000000000..628b8d248 --- /dev/null +++ b/scripts/create_freeipa_enroll_envfile.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python +# Copyright 2015 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Generate an environment file to enroll the controller nodes to FreeIPA via the +ExtraConfigPre hook. + +Please note that this is only used for testing. +""" + +from __future__ import print_function + +import argparse +import collections +import logging +import os +from six.moves import input +import yaml + +import tripleo_common.constants as tc_constants + + +logging.basicConfig(level=logging.INFO) +LOG = logging.getLogger(__name__) + + +autogen_warning = """### DO NOT MODIFY THIS FILE +### created by the script create_freeipa_enroll_envfile.py + +""" + + +TEMPLATE_DEFAULT_PATH = os.path.join( + tc_constants.DEFAULT_TEMPLATES_PATH, + 'puppet/extraconfig/tls/freeipa-enroll.yaml') + + +class TemplateDumper(yaml.SafeDumper): + def represent_ordered_dict(self, data): + return self.represent_dict(data.items()) + + +TemplateDumper.add_representer(collections.OrderedDict, + TemplateDumper.represent_ordered_dict) + + +def write_env_file(output_file, env_dict): + with open(output_file, 'w') as f: + f.write(autogen_warning) + yaml.dump(env_dict, f, TemplateDumper, width=68, + default_flow_style=False) + LOG.info("environment file written to %s" % os.path.abspath(output_file)) + + +def get_freeipa_parameter_defaults_dict(password, server, domain, + dns_servers, ipa_ip): + parameter_defaults = { + 'FreeIPAOTP': password, + 'FreeIPAServer': server, + 'CloudDomain': domain, + } + if dns_servers: + parameter_defaults['DnsServers'] = dns_servers + if ipa_ip: + parameter_defaults['FreeIPAIPAddress'] = ipa_ip + return parameter_defaults + + +def get_freeipa_resource_registry_dict(stack): + resource_registry = { + 'OS::TripleO::NodeTLSCAData': os.path.abspath(stack) + } + return resource_registry + + +def _form_fqdn(name, domain): + return "%s.%s" % (name, domain) + + +def get_cloud_names_parameter_defaults_dict(cloud_name, + cloud_name_internal, + cloud_name_storage, + cloud_name_storage_management, + cloud_name_ctlplane, + cloud_domain): + return { + 'CloudName': _form_fqdn(cloud_name, cloud_domain), + 'CloudNameInternal': _form_fqdn(cloud_name_internal, cloud_domain), + 'CloudNameStorage': _form_fqdn(cloud_name_storage, cloud_domain), + 'CloudNameStorageManagement': _form_fqdn(cloud_name_storage_management, + cloud_domain), + 'CloudNameCtlplane': _form_fqdn(cloud_name_ctlplane, cloud_domain), + } + + +def get_freeipa_environment_dict(password, server, domain, dns_servers, ipa_ip, + stack, cloud_name, cloud_name_internal, + cloud_name_storage, + cloud_name_storage_management, + cloud_name_ctlplane): + enroll_dict = get_freeipa_parameter_defaults_dict(password, server, domain, + dns_servers, ipa_ip) + names_dict = get_cloud_names_parameter_defaults_dict( + cloud_name, + cloud_name_internal, + cloud_name_storage, + cloud_name_storage_management, + cloud_name_ctlplane, + domain) + enroll_dict.update(names_dict) + return get_environment_dict(enroll_dict, + get_freeipa_resource_registry_dict(stack)) + + +def get_environment_dict(parameter_defaults=None, resource_registry=None): + resulting_dict = collections.OrderedDict() + if parameter_defaults: + resulting_dict['parameter_defaults'] = parameter_defaults + if resource_registry: + resulting_dict['resource_registry'] = resource_registry + return resulting_dict + + +def _confirm(message): + user_input = input(message) + return user_input.lower() in ['true', '1', 't', 'y', 'yes'] + + +def _confirmation_if_output_file_exists(output, overwrite): + if os.path.isfile(output) and not overwrite: + LOG.warning("%s exists in the filesystem." % output) + if not _confirm("Do you want to overwrite it? "): + raise RuntimeError("%s exists and won't be overwritten." % output) + + +def _assert_stack_file_exists(stack): + if not os.path.isfile(stack): + raise IOError("%s file doesn't exist." % stack) + + +def _assert_not_empty(server, domain): + if not server or not domain: + raise RuntimeError( + "FreeIPA's server and the domain name can't be empty") + + +def _warn_unmatching_domain(server, domain): + if not server.endswith(domain): + LOG.warning(("FreeIPA's server domain doesn't seem to match the given " + "domain %s ... watch out") % domain) + + +def _validate_input(args): + _confirmation_if_output_file_exists(args.output, args.overwrite) + _assert_stack_file_exists(args.stack) + _assert_not_empty(args.server, args.domain) + _warn_unmatching_domain(args.server, args.domain) + + +def _get_options(): + parser = argparse.ArgumentParser(description=__doc__) + # Base stack arguments + parser.add_argument('-w', '--password', required=True, + help='The OTP that will be used for the nodes.') + parser.add_argument('-s', '--server', required=True, + help="The FreeIPA server's fqdn.") + parser.add_argument('-d', '--domain', required=True, + help=("The FreeIPA managed domain (must match the " + "kerberos realm.")) + parser.add_argument('-D', '--dns-server', action='append', + help=("The DNS server(s) that the overcloud should " + "have configured.")) + parser.add_argument('-i', '--ipa-ip', + help="The FreeIPA server's IP address.") + parser.add_argument('-S', '--stack', + default=TEMPLATE_DEFAULT_PATH, + help=("stack template that will be used if not " + "default.")) + parser.add_argument('-o', '--output', + default='freeipa-enroll.yaml', + help=('file that the freeipa-related environment will ' + 'be written to.')) + + # Cloud name environment arguments + parser.add_argument('--cloud-name', + default='overcloud', + help=("The shortname for the overcloud (the domain " + "will be appended to this).")) + parser.add_argument('--cloud-name-internal', + default='overcloud.internalapi', + help=("The shortname name of the overcloud's internal " + "API endpoint (the domain will be appended to " + "this).")) + parser.add_argument('--cloud-name-storage', + default='overcloud.storage', + help=("The shortname name of the overcloud's storage " + "endpoint (the domain will be appended to " + "this).")) + parser.add_argument('--cloud-name-storage-management', + default='overcloud.storagemgmt', + help=("The shortname name of the overcloud's storage " + " management endpoint (the domain will be " + "appended to this).")) + parser.add_argument('--cloud-name-ctlplane', + default='overcloud.ctlplane', + help=("The shortname name of the overcloud's " + "ctlplane endpoint (the domain will be " + "appended to this).")) + + # Extra + parser.add_argument('--overwrite', action='store_true', + help='overwrite the output file if it already exists.') + return parser.parse_args() + + +def main(): + args = _get_options() + _validate_input(args) + enroll_dict = get_freeipa_environment_dict( + args.password, args.server, args.domain, args.dns_server, args.ipa_ip, + args.stack, args.cloud_name, args.cloud_name_internal, + args.cloud_name_storage, args.cloud_name_storage_management, + args.cloud_name_ctlplane) + write_env_file(args.output, enroll_dict) + + +if __name__ == '__main__': + main() diff --git a/setup.cfg b/setup.cfg index 4677c20f0..7b7261cb5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,7 @@ scripts = scripts/upload-puppet-modules scripts/upload-swift-artifacts scripts/run-validation + scripts/create_freeipa_enroll_envfile.py data_files = lib/heat/undercloud_heat_plugins = undercloud_heat_plugins/*