diff options
Diffstat (limited to 'lib/leap_cli/commands/compile.rb')
-rw-r--r-- | lib/leap_cli/commands/compile.rb | 384 |
1 files changed, 0 insertions, 384 deletions
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb deleted file mode 100644 index a14c267..0000000 --- a/lib/leap_cli/commands/compile.rb +++ /dev/null @@ -1,384 +0,0 @@ -require 'socket' - -module LeapCli - module Commands - - desc "Compile generated files." - command [:compile, :c] do |c| - c.desc 'Compiles node configuration files into hiera files used for deployment.' - c.arg_name 'ENVIRONMENT', :optional => true - c.command :all do |all| - all.action do |global_options,options,args| - environment = args.first - if !LeapCli.leapfile.environment.nil? && !environment.nil? && environment != LeapCli.leapfile.environment - bail! "You cannot specify an ENVIRONMENT argument while the environment is pinned." - end - if environment - if manager.environment_names.include?(environment) - compile_hiera_files(manager.filter([environment]), false) - else - bail! "There is no environment named `#{environment}`." - end - else - clean_export = LeapCli.leapfile.environment.nil? - compile_hiera_files(manager.filter, clean_export) - end - if file_exists?(:static_web_readme) - compile_provider_json(environment) - end - end - end - - c.desc "Compile a DNS zone file for your provider." - c.command :zone do |zone| - zone.action do |global_options, options, args| - compile_zone_file - end - end - - c.desc "Compile provider.json bootstrap files for your provider." - c.command 'provider.json' do |provider| - provider.action do |global_options, options, args| - compile_provider_json - end - end - - c.desc "Generate a list of firewall rules. These rules are already "+ - "implemented on each node, but you might want the list of all "+ - "rules in case you also have a restrictive network firewall." - c.command :firewall do |zone| - zone.action do |global_options, options, args| - compile_firewall - end - end - - c.default_command :all - end - - protected - - # - # a "clean" export of secrets will also remove keys that are no longer used, - # but this should not be done if we are not examining all possible nodes. - # - def compile_hiera_files(nodes, clean_export) - update_compiled_ssh_configs # must come first - sanity_check(nodes) - manager.export_nodes(nodes) - manager.export_secrets(clean_export) - end - - def update_compiled_ssh_configs - generate_monitor_ssh_keys - update_authorized_keys - update_known_hosts - end - - def sanity_check(nodes) - # confirm that every node has a unique ip address - ips = {} - nodes.pick_fields('ip_address').each do |name, ip_address| - if ips.key?(ip_address) - bail! { - log(:fatal_error, "Every node must have its own IP address.") { - log "Nodes `#{name}` and `#{ips[ip_address]}` are both configured with `#{ip_address}`." - } - } - else - ips[ip_address] = name - end - end - # confirm that the IP address of this machine is not also used for a node. - Socket.ip_address_list.each do |addrinfo| - if !addrinfo.ipv4_private? && ips.key?(addrinfo.ip_address) - ip = addrinfo.ip_address - name = ips[ip] - bail! { - log(:fatal_error, "Something is very wrong. The `leap` command must only be run on your sysadmin machine, not on a provider node.") { - log "This machine has the same IP address (#{ip}) as node `#{name}`." - } - } - end - end - end - - ## - ## SSH - ## - - # - # generates a ssh key pair that is used only by remote monitors - # to connect to nodes and run certain allowed commands. - # - # every node has the public monitor key added to their authorized - # keys, and every monitor node has a copy of the private monitor key. - # - def generate_monitor_ssh_keys - priv_key_file = path(:monitor_priv_key) - pub_key_file = path(:monitor_pub_key) - unless file_exists?(priv_key_file, pub_key_file) - ensure_dir(File.dirname(priv_key_file)) - ensure_dir(File.dirname(pub_key_file)) - cmd = %(ssh-keygen -N '' -C 'monitor' -t rsa -b 4096 -f '%s') % priv_key_file - assert_run! cmd - if file_exists?(priv_key_file, pub_key_file) - log :created, priv_key_file - log :created, pub_key_file - else - log :failed, 'to create monitor ssh keys' - end - end - end - - # - # Compiles the authorized keys file, which gets installed on every during init. - # Afterwards, puppet installs an authorized keys file that is generated differently - # (see authorized_keys() in macros.rb) - # - def update_authorized_keys - buffer = StringIO.new - keys = Dir.glob(path([:user_ssh, '*'])) - if keys.empty? - bail! "You must have at least one public SSH user key configured in order to proceed. See `leap help add-user`." - end - if file_exists?(path(:monitor_pub_key)) - keys << path(:monitor_pub_key) - end - keys.sort.each do |keyfile| - ssh_type, ssh_key = File.read(keyfile).strip.split(" ") - buffer << ssh_type - buffer << " " - buffer << ssh_key - buffer << " " - buffer << Path.relative_path(keyfile) - buffer << "\n" - end - write_file!(:authorized_keys, buffer.string) - end - - # - # generates the known_hosts file. - # - # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow - # for the possibility that the hostnames or ip has changed in the node configuration. - # - def update_known_hosts - buffer = StringIO.new - buffer << "#\n" - buffer << "# This file is automatically generated by the command `leap`. You should NOT modify this file.\n" - buffer << "# Instead, rerun `leap node init` on whatever node is causing SSH problems.\n" - buffer << "#\n" - manager.nodes.keys.sort.each do |node_name| - node = manager.nodes[node_name] - hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',') - pub_key = read_file([:node_ssh_pub_key,node.name]) - if pub_key - buffer << [hostnames, pub_key].join(' ') - buffer << "\n" - end - end - write_file!(:known_hosts, buffer.string) - end - - ## - ## provider.json - ## - - # - # generates static provider.json files that can put into place - # (e.g. https://domain/provider.json) for the cases where the - # webapp domain does not match the provider's domain. - # - def compile_provider_json(environments=nil) - webapp_nodes = manager.nodes[:services => 'webapp'] - write_file!(:static_web_readme, STATIC_WEB_README) - environments ||= manager.environment_names - environments.each do |env| - node = webapp_nodes[:environment => env].values.first - if node - env ||= 'default' - write_file!( - [:static_web_provider_json, env], - node['definition_files']['provider'] - ) - write_file!( - [:static_web_htaccess, env], - HTACCESS_FILE % {:min_version => manager.env(env).provider.client_version['min']} - ) - end - end - end - - HTACCESS_FILE = %[ - <Location /provider.json> - Header set X-Minimum-Client-Version %{min_version} - </Location> -] - - STATIC_WEB_README = %[ -This directory contains statically rendered copies of the `provider.json` file -used by the client to "bootstrap" configure itself for use with your service -provider. - -There is a separate provider.json file for each environment, although you -should only need 'production/provider.json' or, if you have no environments -configured, 'default/provider.json'. - -To clarify, this is the public `provider.json` file used by the client, not the -`provider.json` file that is used to configure the provider. - -The provider.json file must be available at `https://domain/provider.json` -(unless this provider is included in the list of providers which are pre- -seeded in client). - -This provider.json file can be served correctly in one of three ways: - -(1) If the property webapp.domain is not configured, then the web app will be - installed at https://domain/ and it will handle serving the provider.json file. - -(2) If one or more nodes have the 'static' service configured for the provider's - domain, then these 'static' nodes will correctly serve provider.json. - -(3) Otherwise, you must copy the provider.json file to your web - server and make it available at '/provider.json'. The example htaccess - file shows what header options should be sent by the web server - with the response. - -This directory is needed for method (3), but not for methods (1) or (2). - -This directory has been created by the command `leap compile provider.json`. -Once created, it will be kept up to date everytime you compile. You may safely -remove this directory if you don't use it. -] - - ## - ## - ## ZONE FILE - ## - - def relative_hostname(fqdn) - @domain_regexp ||= /\.?#{Regexp.escape(provider.domain)}$/ - fqdn.sub(@domain_regexp, '') - end - - # - # serial is any number less than 2^32 (4294967296) - # - def compile_zone_file - hosts_seen = {} - f = $stdout - f.puts ZONE_HEADER % {:domain => provider.domain, :ns => provider.domain, :contact => provider.contacts.default.first.sub('@','.')} - max_width = manager.nodes.values.inject(0) {|max, node| [max, relative_hostname(node.domain.full).length].max } - put_line = lambda do |host, line| - host = '@' if host == '' - f.puts("%-#{max_width}s %s" % [host, line]) - end - - f.puts ORIGIN_HEADER - # 'A' records for primary domain - manager.nodes[:environment => '!local'].each_node do |node| - if node.dns['aliases'] && node.dns.aliases.include?(provider.domain) - put_line.call "", "IN A #{node.ip_address}" - end - end - - # NS records - if provider['dns'] && provider.dns['nameservers'] - provider.dns.nameservers.each do |ns| - put_line.call "", "IN NS #{ns}." - end - end - - # all other records - manager.environment_names.each do |env| - next if env == 'local' - nodes = manager.nodes[:environment => env] - next unless nodes.any? - f.puts ENV_HEADER % (env.nil? ? 'default' : env) - nodes.each_node do |node| - if node.dns.public - hostname = relative_hostname(node.domain.full) - put_line.call relative_hostname(node.domain.full), "IN A #{node.ip_address}" - end - if node.dns['aliases'] - node.dns.aliases.each do |host_alias| - if host_alias != node.domain.full && host_alias != provider.domain - put_line.call relative_hostname(host_alias), "IN A #{node.ip_address}" - end - end - end - if node.services.include? 'mx' - put_line.call relative_hostname(node.domain.full_suffix), "IN MX 10 #{relative_hostname(node.domain.full)}" - end - end - end - end - - ENV_HEADER = %[ -;; -;; ENVIRONMENT %s -;; - -] - - ZONE_HEADER = %[ -;; -;; BIND data file for %{domain} -;; - -$TTL 600 -$ORIGIN %{domain}. - -@ IN SOA %{ns}. %{contact}. ( - 0000 ; serial - 7200 ; refresh ( 24 hours) - 3600 ; retry ( 2 hours) - 1209600 ; expire (1000 hours) - 600 ) ; minimum ( 2 days) -; -] - - ORIGIN_HEADER = %[ -;; -;; ZONE ORIGIN -;; - -] - - ## - ## FIREWALL - ## - - def compile_firewall - manager.nodes.each_node(&:evaluate) - - rules = [["ALLOW TO", "PORTS", "ALLOW FROM"]] - manager.nodes[:environment => '!local'].values.each do |node| - next unless node['firewall'] - node.firewall.each do |name, rule| - if rule.is_a? Hash - rules << add_rule(rule) - elsif rule.is_a? Array - rule.each do |r| - rules << add_rule(r) - end - end - end - end - - max_to = rules.inject(0) {|max, r| [max, r[0].length].max} - max_port = rules.inject(0) {|max, r| [max, r[1].length].max} - max_from = rules.inject(0) {|max, r| [max, r[2].length].max} - rules.each do |rule| - puts "%-#{max_to}s %-#{max_port}s %-#{max_from}s" % rule - end - end - - private - - def add_rule(rule) - [rule["to"], [rule["port"]].compact.join(','), rule["from"]] - end - - end -end
\ No newline at end of file |