httpd_configmap_generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +1 -0
- data/.travis.yml +14 -0
- data/Dockerfile +16 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README-active-directory.md +38 -0
- data/README-ipa.md +41 -0
- data/README-saml.md +70 -0
- data/README.md +386 -0
- data/Rakefile +8 -0
- data/bin/httpd_configmap_generator +101 -0
- data/httpd_configmap_generator.gemspec +34 -0
- data/lib/httpd_configmap_generator.rb +29 -0
- data/lib/httpd_configmap_generator/active_directory.rb +114 -0
- data/lib/httpd_configmap_generator/base.rb +83 -0
- data/lib/httpd_configmap_generator/base/command.rb +29 -0
- data/lib/httpd_configmap_generator/base/config.rb +13 -0
- data/lib/httpd_configmap_generator/base/config_map.rb +183 -0
- data/lib/httpd_configmap_generator/base/file.rb +66 -0
- data/lib/httpd_configmap_generator/base/kerberos.rb +13 -0
- data/lib/httpd_configmap_generator/base/network.rb +37 -0
- data/lib/httpd_configmap_generator/base/pam.rb +9 -0
- data/lib/httpd_configmap_generator/base/principal.rb +33 -0
- data/lib/httpd_configmap_generator/base/sssd.rb +51 -0
- data/lib/httpd_configmap_generator/export.rb +31 -0
- data/lib/httpd_configmap_generator/ipa.rb +122 -0
- data/lib/httpd_configmap_generator/options.rb +13 -0
- data/lib/httpd_configmap_generator/saml.rb +104 -0
- data/lib/httpd_configmap_generator/update.rb +39 -0
- data/lib/httpd_configmap_generator/version.rb +3 -0
- data/templates/etc/pam.d/httpd-auth +2 -0
- data/templates/httpd-configmap-generator-template.yaml +113 -0
- metadata +203 -0
    
        data/Rakefile
    ADDED
    
    
| @@ -0,0 +1,101 @@ | |
| 1 | 
            +
            #! /usr/bin/env ruby
         | 
| 2 | 
            +
            #
         | 
| 3 | 
            +
            # Script for configuring container with external authentication
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # Stdout: standard processing messages ...
         | 
| 6 | 
            +
            # Stderr: any errors during configuration ...
         | 
| 7 | 
            +
            # -o filename: for the generated auth-config map.
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require "bundler/setup"
         | 
| 11 | 
            +
            require "trollop"
         | 
| 12 | 
            +
            require "httpd_configmap_generator"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            CMD = File.basename($PROGRAM_NAME)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            def error_msg(msg)
         | 
| 17 | 
            +
              $stderr.puts msg
         | 
| 18 | 
            +
              exit 1
         | 
| 19 | 
            +
            end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            module HttpdConfigmapGenerator
         | 
| 22 | 
            +
              class Cli
         | 
| 23 | 
            +
                def parse_arguments(auth_config)
         | 
| 24 | 
            +
                  opts = Trollop.options do
         | 
| 25 | 
            +
                    version("#{CMD} #{HttpdConfigmapGenerator::VERSION} - External Authentication Configuration script")
         | 
| 26 | 
            +
                    banner <<-EOS
         | 
| 27 | 
            +
            #{version}
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            Usage: #{CMD} auth_type | update | export [--help | options]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            #{CMD} options are:
         | 
| 32 | 
            +
                  EOS
         | 
| 33 | 
            +
                    opt :version, "Version of the #{CMD} command",
         | 
| 34 | 
            +
                        :default => false, :short => "-V"
         | 
| 35 | 
            +
                    auth_config.required_options.each do |key, key_options|
         | 
| 36 | 
            +
                      opt key, key_options[:description], HttpdConfigmapGenerator::Cli.options_for(key_options, true)
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                    auth_config.optional_options.each do |key, key_options|
         | 
| 39 | 
            +
                      opt key, key_options[:description], HttpdConfigmapGenerator::Cli.options_for(key_options)
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                  opts
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def run_configure(auth_type)
         | 
| 46 | 
            +
                  begin
         | 
| 47 | 
            +
                    auth_config = HttpdConfigmapGenerator.new_config(auth_type)
         | 
| 48 | 
            +
                  rescue => err
         | 
| 49 | 
            +
                    error_msg(err.to_s)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  auth_config.run_configure(parse_arguments(auth_config))
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def run_update
         | 
| 56 | 
            +
                  begin
         | 
| 57 | 
            +
                    auth_config = HttpdConfigmapGenerator::Update.new
         | 
| 58 | 
            +
                  rescue => err
         | 
| 59 | 
            +
                    error_msg(err.to_s)
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                  auth_config.update(parse_arguments(auth_config))
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def run_export
         | 
| 66 | 
            +
                  begin
         | 
| 67 | 
            +
                    auth_config = HttpdConfigmapGenerator::Export.new
         | 
| 68 | 
            +
                  rescue => err
         | 
| 69 | 
            +
                    error_msg(err.to_s)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  auth_config.export(parse_arguments(auth_config))
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def self.options_for(key_options, required = false)
         | 
| 76 | 
            +
                  options = {}
         | 
| 77 | 
            +
                  options[:default] = key_options.key?(:default) ? key_options[:default] : ""
         | 
| 78 | 
            +
                  options[:required] = true if required
         | 
| 79 | 
            +
                  options[:short]    = key_options[:short] if key_options[:short]
         | 
| 80 | 
            +
                  options[:multi]    = key_options[:multi] if key_options[:multi]
         | 
| 81 | 
            +
                  options
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            if ARGV.empty?
         | 
| 87 | 
            +
              error_msg("
         | 
| 88 | 
            +
            Usage: #{CMD} auth_type | update | export [--help | options]
         | 
| 89 | 
            +
            Supported auth_type: #{HttpdConfigmapGenerator.supported_auth_types.join(', ')}
         | 
| 90 | 
            +
            ")
         | 
| 91 | 
            +
            else
         | 
| 92 | 
            +
              auth_type = ARGV.shift
         | 
| 93 | 
            +
              case auth_type
         | 
| 94 | 
            +
              when "update"
         | 
| 95 | 
            +
                HttpdConfigmapGenerator::Cli.new.run_update
         | 
| 96 | 
            +
              when "export"
         | 
| 97 | 
            +
                HttpdConfigmapGenerator::Cli.new.run_export
         | 
| 98 | 
            +
              else
         | 
| 99 | 
            +
                HttpdConfigmapGenerator::Cli.new.run_configure(auth_type)
         | 
| 100 | 
            +
              end
         | 
| 101 | 
            +
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            lib = File.expand_path("../lib", __FILE__)
         | 
| 2 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # Declare Gem's Version
         | 
| 5 | 
            +
            require "httpd_configmap_generator/version"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # Declare Dependencies
         | 
| 8 | 
            +
            Gem::Specification.new do |s|
         | 
| 9 | 
            +
              s.name          = "httpd_configmap_generator"
         | 
| 10 | 
            +
              s.version       = HttpdConfigmapGenerator::VERSION
         | 
| 11 | 
            +
              s.authors       = ["Httpd Auth Config Developers"]
         | 
| 12 | 
            +
              s.homepage      = "https://github.com/ManageIQ/httpd_configmap_generator"
         | 
| 13 | 
            +
              s.summary       = "The Httpd Configmap Generator"
         | 
| 14 | 
            +
              s.description   = "The Httpd Configmap Generator"
         | 
| 15 | 
            +
              s.licenses      = ["Apache-2.0"]
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              s.files         = `git ls-files -z`.split("\x0").reject do |f|
         | 
| 18 | 
            +
                f.match(%r{^(test|spec|features)/})
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
              s.bindir        = "bin"
         | 
| 21 | 
            +
              s.executables   = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 22 | 
            +
              s.require_paths = ["lib"]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              s.add_development_dependency "codeclimate-test-reporter", "~> 1.0.0"
         | 
| 25 | 
            +
              s.add_development_dependency "rspec",    "~> 3.0"
         | 
| 26 | 
            +
              s.add_development_dependency "rake"
         | 
| 27 | 
            +
              s.add_development_dependency "simplecov"
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              s.add_dependency "activesupport",        ">=5.0"
         | 
| 30 | 
            +
              s.add_dependency "awesome_spawn",        "~> 1.4"
         | 
| 31 | 
            +
              s.add_dependency "iniparse",             "~> 1.4"
         | 
| 32 | 
            +
              s.add_dependency "more_core_extensions", "~> 3.4"
         | 
| 33 | 
            +
              s.add_dependency "trollop",              "~> 2.1"
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require "httpd_configmap_generator/version"
         | 
| 2 | 
            +
            require "httpd_configmap_generator/base"
         | 
| 3 | 
            +
            require "httpd_configmap_generator/active_directory"
         | 
| 4 | 
            +
            require "httpd_configmap_generator/ipa"
         | 
| 5 | 
            +
            require "httpd_configmap_generator/saml"
         | 
| 6 | 
            +
            require "httpd_configmap_generator/update"
         | 
| 7 | 
            +
            require "httpd_configmap_generator/export"
         | 
| 8 | 
            +
            require "more_core_extensions/core_ext/hash"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module HttpdConfigmapGenerator
         | 
| 11 | 
            +
              def self.new_config(auth_type)
         | 
| 12 | 
            +
                auth_class(auth_type).new
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def self.supported_auth_types
         | 
| 16 | 
            +
                constants.collect do |c|
         | 
| 17 | 
            +
                  k = const_get(c)
         | 
| 18 | 
            +
                  k::AUTH[:subtype] if k.kind_of?(Class) && k.constants.include?(:AUTH)
         | 
| 19 | 
            +
                end.compact
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def self.auth_class(auth_type)
         | 
| 23 | 
            +
                require "active_support/core_ext/string" # for camelize
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                auth_type = auth_type.tr('-', '_').camelize
         | 
| 26 | 
            +
                raise "Invalid Authentication Type #{auth_type} specified" unless const_defined?(auth_type, false)
         | 
| 27 | 
            +
                const_get(auth_type, false)
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            module HttpdConfigmapGenerator
         | 
| 2 | 
            +
              class ActiveDirectory < Base
         | 
| 3 | 
            +
                REALM_COMMAND        = "/usr/sbin/realm".freeze
         | 
| 4 | 
            +
                KERBEROS_KEYTAB_FILE = "/etc/krb5.keytab".freeze
         | 
| 5 | 
            +
                AUTH = {
         | 
| 6 | 
            +
                  :type    => "active-directory",
         | 
| 7 | 
            +
                  :subtype => "active-directory"
         | 
| 8 | 
            +
                }.freeze
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def required_options
         | 
| 11 | 
            +
                  super.merge(
         | 
| 12 | 
            +
                    :ad_domain   => { :description => "Active Directory Domain"   },
         | 
| 13 | 
            +
                    :ad_user     => { :description => "Active Directory User"     },
         | 
| 14 | 
            +
                    :ad_password => { :description => "Active Directory Password" }
         | 
| 15 | 
            +
                  )
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def optional_options
         | 
| 19 | 
            +
                  super.merge(
         | 
| 20 | 
            +
                    :ad_realm  => { :description => "Active Directory Realm"  },
         | 
| 21 | 
            +
                    :ad_server => { :description => "Active Directory Server" }
         | 
| 22 | 
            +
                  )
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def persistent_files
         | 
| 26 | 
            +
                  %w(
         | 
| 27 | 
            +
                    /etc/krb5.keytab
         | 
| 28 | 
            +
                    /etc/krb5.conf
         | 
| 29 | 
            +
                    /etc/nsswitch.conf
         | 
| 30 | 
            +
                    /etc/openldap/ldap.conf
         | 
| 31 | 
            +
                    /etc/pam.d/fingerprint-auth-ac
         | 
| 32 | 
            +
                    /etc/pam.d/httpd-auth
         | 
| 33 | 
            +
                    /etc/pam.d/password-auth-ac
         | 
| 34 | 
            +
                    /etc/pam.d/postlogin-ac
         | 
| 35 | 
            +
                    /etc/pam.d/smartcard-auth-ac
         | 
| 36 | 
            +
                    /etc/pam.d/system-auth-ac
         | 
| 37 | 
            +
                    /etc/resolv.conf
         | 
| 38 | 
            +
                    /etc/sssd/sssd.conf
         | 
| 39 | 
            +
                    /etc/sysconfig/authconfig
         | 
| 40 | 
            +
                    /etc/sysconfig/network
         | 
| 41 | 
            +
                  )
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def configure(opts)
         | 
| 45 | 
            +
                  update_hostname(opts[:host])
         | 
| 46 | 
            +
                  join_ad_realm
         | 
| 47 | 
            +
                  realm_permit_all
         | 
| 48 | 
            +
                  configure_pam
         | 
| 49 | 
            +
                  configure_sssd
         | 
| 50 | 
            +
                  update_kerberos_keytab_permissions
         | 
| 51 | 
            +
                  enable_kerberos_dns_lookups
         | 
| 52 | 
            +
                  config_map = ConfigMap.new(opts)
         | 
| 53 | 
            +
                  config_map.generate(AUTH[:type], realm, persistent_files)
         | 
| 54 | 
            +
                  config_map.save(opts[:output])
         | 
| 55 | 
            +
                rescue => err
         | 
| 56 | 
            +
                  log_command_error(err)
         | 
| 57 | 
            +
                  raise err
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def configured?
         | 
| 61 | 
            +
                  File.exist?(SSSD_CONFIG)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def unconfigure
         | 
| 65 | 
            +
                  return unless configured?
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def realm
         | 
| 69 | 
            +
                  @realm ||= opts[:ad_realm] if opts[:ad_realm].present?
         | 
| 70 | 
            +
                  @realm ||= domain
         | 
| 71 | 
            +
                  @realm ||= super
         | 
| 72 | 
            +
                  @realm = @realm.upcase
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def domain
         | 
| 76 | 
            +
                  @domain ||= opts[:ad_domain] if opts[:ad_domain].present?
         | 
| 77 | 
            +
                  @domain ||= super
         | 
| 78 | 
            +
                  @domain
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                private
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def configure_sssd
         | 
| 84 | 
            +
                  info_msg("Configuring SSSD Service")
         | 
| 85 | 
            +
                  sssd = Sssd.new(opts)
         | 
| 86 | 
            +
                  sssd.load(SSSD_CONFIG)
         | 
| 87 | 
            +
                  sssd.configure_domain(domain)
         | 
| 88 | 
            +
                  sssd.section("domain/#{domain}")["ad_server"] = opts[:ad_server] if opts[:ad_server].present?
         | 
| 89 | 
            +
                  sssd.section("sssd")["domains"] = domain
         | 
| 90 | 
            +
                  sssd.section("sssd")["default_domain_suffix"] = domain
         | 
| 91 | 
            +
                  sssd.add_service("pam")
         | 
| 92 | 
            +
                  sssd.configure_ifp
         | 
| 93 | 
            +
                  debug_msg("- Creating #{SSSD_CONFIG}")
         | 
| 94 | 
            +
                  sssd.save(SSSD_CONFIG)
         | 
| 95 | 
            +
                end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                def join_ad_realm
         | 
| 98 | 
            +
                  info_msg("Joining the AD Realm ...")
         | 
| 99 | 
            +
                  debug_msg(" - realm join #{realm} ...")
         | 
| 100 | 
            +
                  command_run!(REALM_COMMAND, :params => ["join", domain, "-U", opts[:ad_user]], :stdin_data => opts[:ad_password])
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def realm_permit_all
         | 
| 104 | 
            +
                  info_msg("Allowing AD Users to Login ...")
         | 
| 105 | 
            +
                  command_run!(REALM_COMMAND, :params => ["permit", "--all"])
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def update_kerberos_keytab_permissions
         | 
| 109 | 
            +
                  info_msg("Updating Kerberos keytab permissions ...")
         | 
| 110 | 
            +
                  FileUtils.chown("apache", "root", KERBEROS_KEYTAB_FILE)
         | 
| 111 | 
            +
                  FileUtils.chmod(0o640, KERBEROS_KEYTAB_FILE)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
              end
         | 
| 114 | 
            +
            end
         | 
| @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            require "pathname"
         | 
| 2 | 
            +
            require "httpd_configmap_generator/base/command"
         | 
| 3 | 
            +
            require "httpd_configmap_generator/base/config"
         | 
| 4 | 
            +
            require "httpd_configmap_generator/base/config_map"
         | 
| 5 | 
            +
            require "httpd_configmap_generator/base/file"
         | 
| 6 | 
            +
            require "httpd_configmap_generator/base/kerberos"
         | 
| 7 | 
            +
            require "httpd_configmap_generator/base/network"
         | 
| 8 | 
            +
            require "httpd_configmap_generator/base/pam"
         | 
| 9 | 
            +
            require "httpd_configmap_generator/base/principal"
         | 
| 10 | 
            +
            require "httpd_configmap_generator/base/sssd"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module HttpdConfigmapGenerator
         | 
| 13 | 
            +
              class Base
         | 
| 14 | 
            +
                APACHE_USER          = "apache".freeze
         | 
| 15 | 
            +
                HTTP_KEYTAB          = "/etc/http.keytab".freeze
         | 
| 16 | 
            +
                IPA_COMMAND          = "/usr/bin/ipa".freeze
         | 
| 17 | 
            +
                KERBEROS_CONFIG_FILE = "/etc/krb5.conf".freeze
         | 
| 18 | 
            +
                LDAP_ATTRS           = {
         | 
| 19 | 
            +
                  "mail"        => "REMOTE_USER_EMAIL",
         | 
| 20 | 
            +
                  "givenname"   => "REMOTE_USER_FIRSTNAME",
         | 
| 21 | 
            +
                  "sn"          => "REMOTE_USER_LASTNAME",
         | 
| 22 | 
            +
                  "displayname" => "REMOTE_USER_FULLNAME",
         | 
| 23 | 
            +
                  "domainname"  => "REMOTE_USER_DOMAIN"
         | 
| 24 | 
            +
                }.freeze
         | 
| 25 | 
            +
                PAM_CONFIG           = "/etc/pam.d/httpd-auth".freeze
         | 
| 26 | 
            +
                SSSD_CONFIG          = "/etc/sssd/sssd.conf".freeze
         | 
| 27 | 
            +
                TIMESTAMP_FORMAT     = "%Y%m%d_%H%M%S".freeze
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                attr_accessor :opts
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def initialize(opts = {})
         | 
| 32 | 
            +
                  @opts = opts
         | 
| 33 | 
            +
                  @realm = @domain = nil
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def err_msg(msg)
         | 
| 37 | 
            +
                  STDERR.puts(msg)
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def info_msg(msg)
         | 
| 41 | 
            +
                  STDOUT.puts(msg)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def debug_msg(msg)
         | 
| 45 | 
            +
                  STDOUT.puts(msg) if opts[:debug]
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def required_options
         | 
| 49 | 
            +
                  {
         | 
| 50 | 
            +
                    :host   => { :description => "Application Domain",
         | 
| 51 | 
            +
                                 :short       => "-h" },
         | 
| 52 | 
            +
                    :output => { :description => "Configuration map file to create",
         | 
| 53 | 
            +
                                 :short       => "-o" }
         | 
| 54 | 
            +
                  }
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def optional_options
         | 
| 58 | 
            +
                  {
         | 
| 59 | 
            +
                    :force => { :description => "Force configuration if configured already",
         | 
| 60 | 
            +
                                :short       => "-f",
         | 
| 61 | 
            +
                                :default     => false },
         | 
| 62 | 
            +
                    :debug => { :description => "Enable debugging",
         | 
| 63 | 
            +
                                :short       => "-d",
         | 
| 64 | 
            +
                                :default     => false }
         | 
| 65 | 
            +
                  }
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def run_configure(opts)
         | 
| 69 | 
            +
                  validate_options(opts)
         | 
| 70 | 
            +
                  @opts = opts
         | 
| 71 | 
            +
                  unconfigure if configured? && opts[:force]
         | 
| 72 | 
            +
                  raise "#{self.class.name} Already Configured" if configured?
         | 
| 73 | 
            +
                  unless ENV["HTTPD_AUTH_TYPE"]
         | 
| 74 | 
            +
                    raise "Not running in httpd_configmap_generator container - Skipping #{self.class.name} configuration"
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                  configure(opts)
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def validate_options(_options)
         | 
| 80 | 
            +
                  nil
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require "awesome_spawn"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module HttpdConfigmapGenerator
         | 
| 4 | 
            +
              class Base
         | 
| 5 | 
            +
                def command_run(executable, options = {})
         | 
| 6 | 
            +
                  if opts && opts[:debug]
         | 
| 7 | 
            +
                    debug_msg("Running Command: #{AwesomeSpawn.build_command_line(executable, options)}")
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
                  AwesomeSpawn.run(executable, options)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def command_run!(executable, options = {})
         | 
| 13 | 
            +
                  if opts && opts[:debug]
         | 
| 14 | 
            +
                    debug_msg("Running Command: #{AwesomeSpawn.build_command_line(executable, options)}")
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                  AwesomeSpawn.run!(executable, options)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def log_command_error(err)
         | 
| 20 | 
            +
                  err_msg("Command Error: #{err}")
         | 
| 21 | 
            +
                  if err.kind_of?(AwesomeSpawn::CommandResultError)
         | 
| 22 | 
            +
                    err_msg("stdout: #{err.result.output}")
         | 
| 23 | 
            +
                    err_msg("stderr: #{err.result.error}")
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    err_msg(err.backtrace)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require "active_support"
         | 
| 2 | 
            +
            require "active_support/core_ext" # for Time.current
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module HttpdConfigmapGenerator
         | 
| 5 | 
            +
              class Base
         | 
| 6 | 
            +
                def config_file_backup(path)
         | 
| 7 | 
            +
                  if File.exist?(path)
         | 
| 8 | 
            +
                    timestamp = Time.current.strftime(TIMESTAMP_FORMAT)
         | 
| 9 | 
            +
                    FileUtils.copy(path, "#{path}.#{timestamp}")
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,183 @@ | |
| 1 | 
            +
            require "base64"
         | 
| 2 | 
            +
            require "yaml"
         | 
| 3 | 
            +
            require "uri"
         | 
| 4 | 
            +
            require "etc"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module HttpdConfigmapGenerator
         | 
| 7 | 
            +
              class ConfigMap < Base
         | 
| 8 | 
            +
                DATA_SECTION = "data".freeze
         | 
| 9 | 
            +
                AUTH_CONFIGURATION = "auth-configuration.conf".freeze
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                attr_accessor :config_map
         | 
| 12 | 
            +
                attr_accessor :opts
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def initialize(opts = {})
         | 
| 15 | 
            +
                  @opts = opts
         | 
| 16 | 
            +
                  @config_map = template
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def generate(auth_type, realm, file_list)
         | 
| 20 | 
            +
                  info_msg("Generating Auth Config-Map for #{auth_type}")
         | 
| 21 | 
            +
                  @config_map = template(auth_type, realm)
         | 
| 22 | 
            +
                  file_specs = gen_filespecs(file_list)
         | 
| 23 | 
            +
                  define_configuration(file_specs)
         | 
| 24 | 
            +
                  include_files(file_specs)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def load(file_path)
         | 
| 28 | 
            +
                  @config_map = File.exist?(file_path) ? YAML.load_file(file_path) : {}
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def save(file_path)
         | 
| 32 | 
            +
                  delete_target_file(file_path)
         | 
| 33 | 
            +
                  info_msg("Saving Auth Config-Map to #{file_path}")
         | 
| 34 | 
            +
                  File.open(file_path, "w") { |f| f.write(config_map.to_yaml) }
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def add_files(file_list)
         | 
| 38 | 
            +
                  return unless file_list
         | 
| 39 | 
            +
                  file_specs = gen_filespecs_for_files_to_add(file_list)
         | 
| 40 | 
            +
                  update_configuration(file_specs)
         | 
| 41 | 
            +
                  include_files(file_specs)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def export_file(file_entry, output_file)
         | 
| 45 | 
            +
                  basename, _target_file, _mode = search_file_entry(file_entry)
         | 
| 46 | 
            +
                  raise "File #{file_entry} does not exist in the configuration map" unless basename
         | 
| 47 | 
            +
                  delete_target_file(output_file)
         | 
| 48 | 
            +
                  create_target_directory(output_file)
         | 
| 49 | 
            +
                  debug_msg("Exporting #{file_entry} to #{output_file} ...")
         | 
| 50 | 
            +
                  content = config_map.fetch_path(DATA_SECTION, basename)
         | 
| 51 | 
            +
                  content = Base64.decode64(content) if basename =~ /^.*\.base64$/
         | 
| 52 | 
            +
                  File.write(output_file, content)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                private
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def template(auth_type = "internal", kerberos_realms = "undefined")
         | 
| 58 | 
            +
                  {
         | 
| 59 | 
            +
                    DATA_SECTION => {
         | 
| 60 | 
            +
                      "auth-type"            => auth_type,
         | 
| 61 | 
            +
                      "auth-kerberos-realms" => kerberos_realms
         | 
| 62 | 
            +
                    },
         | 
| 63 | 
            +
                    "kind"       => "ConfigMap",
         | 
| 64 | 
            +
                    "metadata"   => {
         | 
| 65 | 
            +
                      "name" => "httpd-auth-configs"
         | 
| 66 | 
            +
                    }
         | 
| 67 | 
            +
                  }
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def gen_filespecs(file_list)
         | 
| 71 | 
            +
                  file_specs = []
         | 
| 72 | 
            +
                  file_list.each do |file|
         | 
| 73 | 
            +
                    file_specs << file_entry_spec(file.strip)
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                  file_specs.sort_by { |file_spec| file_spec[:basename] }
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                # Supporting the following signatures:
         | 
| 79 | 
            +
                #   /path/of/real/file
         | 
| 80 | 
            +
                #   /path/of/source/file,/path/of/real/file
         | 
| 81 | 
            +
                #   /path/of/source/file,/path/of/real/file,mode
         | 
| 82 | 
            +
                #   http://url_source,/path/of/real/file,mode
         | 
| 83 | 
            +
                def gen_filespecs_for_files_to_add(file_list)
         | 
| 84 | 
            +
                  file_specs = []
         | 
| 85 | 
            +
                  file_list.each do |file_to_add|
         | 
| 86 | 
            +
                    file_spec = file_to_add.split(",").map(&:strip)
         | 
| 87 | 
            +
                    case file_spec.length
         | 
| 88 | 
            +
                    when 1
         | 
| 89 | 
            +
                      file = file_spec.first
         | 
| 90 | 
            +
                      file_entry = file_entry_spec(file)
         | 
| 91 | 
            +
                    when 2
         | 
| 92 | 
            +
                      source_file, target_file = file_spec
         | 
| 93 | 
            +
                      raise "Must specify a mode for URL file sources" if source_file =~ URI.regexp(%w(http https))
         | 
| 94 | 
            +
                      file_entry = file_entry_spec(source_file, target_file)
         | 
| 95 | 
            +
                    when 3
         | 
| 96 | 
            +
                      source_file, target_file, mode = file_spec
         | 
| 97 | 
            +
                      if source_file =~ URI.regexp(%w(http https))
         | 
| 98 | 
            +
                        fetch_network_file(source_file, target_file)
         | 
| 99 | 
            +
                        file_entry = file_entry_spec(target_file, target_file, mode)
         | 
| 100 | 
            +
                      else
         | 
| 101 | 
            +
                        file_entry = file_entry_spec(source_file, target_file, mode)
         | 
| 102 | 
            +
                      end
         | 
| 103 | 
            +
                    else
         | 
| 104 | 
            +
                      raise "Invalid file specification #{file_to_add}"
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                    file_specs << file_entry
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                  file_specs.sort_by { |file_spec| file_spec[:basename] }
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                def file_entry_spec(source_file, target_file = nil, mode = nil)
         | 
| 112 | 
            +
                  target_file = source_file.dup unless target_file
         | 
| 113 | 
            +
                  unless mode
         | 
| 114 | 
            +
                    stat = File.stat(source_file)
         | 
| 115 | 
            +
                    file_owner = Etc.getpwuid(stat.uid).name
         | 
| 116 | 
            +
                    file_group = Etc.getgrgid(stat.gid).name
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                  {
         | 
| 119 | 
            +
                    :basename => File.basename(target_file).dup,
         | 
| 120 | 
            +
                    :binary   => file_binary?(source_file),
         | 
| 121 | 
            +
                    :target   => target_file,
         | 
| 122 | 
            +
                    :mode     => mode ? mode : "%4o:%s:%s" % [stat.mode & 0o7777, file_owner, file_group]
         | 
| 123 | 
            +
                  }
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def update_configuration(file_specs)
         | 
| 127 | 
            +
                  auth_configuration = fetch_auth_configuration
         | 
| 128 | 
            +
                  return define_configuration(file_specs) unless auth_configuration
         | 
| 129 | 
            +
                  # first, remove any file_specs references in the file list, we don't want duplication here.
         | 
| 130 | 
            +
                  auth_configuration = auth_configuration.split("\n")
         | 
| 131 | 
            +
                  file_specs.each do |file_spec|
         | 
| 132 | 
            +
                    entry = auth_configuration.select { |line| line =~ file_entry_regex(file_spec[:target]) }
         | 
| 133 | 
            +
                    auth_configuration -= entry if entry
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                  auth_configuration = auth_configuration.join("\n") + "\n"
         | 
| 136 | 
            +
                  # now, append any of the new file_specs at the end of the list.
         | 
| 137 | 
            +
                  append_configuration(auth_configuration, file_specs)
         | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def search_file_entry(target_file)
         | 
| 141 | 
            +
                  auth_configuration = fetch_auth_configuration
         | 
| 142 | 
            +
                  return nil unless auth_configuration
         | 
| 143 | 
            +
                  auth_configuration = auth_configuration.split("\n")
         | 
| 144 | 
            +
                  entry = auth_configuration.select { |line| line =~ file_entry_regex(target_file) }
         | 
| 145 | 
            +
                  entry ? entry.first.split('=')[1].strip.split(' ') : nil
         | 
| 146 | 
            +
                end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                def define_configuration(file_specs)
         | 
| 149 | 
            +
                  auth_configuration = "# External Authentication Configuration File\n#\n"
         | 
| 150 | 
            +
                  append_configuration(auth_configuration, file_specs)
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                def include_files(file_specs)
         | 
| 154 | 
            +
                  file_specs.each do |file_spec|
         | 
| 155 | 
            +
                    content = File.read(file_spec[:target])
         | 
| 156 | 
            +
                    content = Base64.encode64(content) if file_spec[:binary]
         | 
| 157 | 
            +
                    # encode(:universal_newline => true) will convert \r\n to \n, necessary for to_yaml to render properly.
         | 
| 158 | 
            +
                    config_map[DATA_SECTION].merge!(file_basename(file_spec) => content.encode(:universal_newline => true))
         | 
| 159 | 
            +
                  end
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                def file_basename(file_spec)
         | 
| 163 | 
            +
                  file_spec[:binary] ? "#{file_spec[:basename]}.base64" : file_spec[:basename]
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                def append_configuration(auth_configuration, file_specs)
         | 
| 167 | 
            +
                  file_specs.each do |file_spec|
         | 
| 168 | 
            +
                    debug_msg("Adding file #{file_spec[:target]} ...")
         | 
| 169 | 
            +
                    auth_configuration += "file = #{file_basename(file_spec)} #{file_spec[:target]} #{file_spec[:mode]}\n"
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
                  config_map[DATA_SECTION] ||= {}
         | 
| 172 | 
            +
                  config_map[DATA_SECTION].merge!(AUTH_CONFIGURATION => auth_configuration)
         | 
| 173 | 
            +
                end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                def fetch_auth_configuration
         | 
| 176 | 
            +
                  config_map.fetch_path(DATA_SECTION, AUTH_CONFIGURATION)
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                def file_entry_regex(target_file)
         | 
| 180 | 
            +
                  /^file = .* #{target_file} .*$/
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
              end
         | 
| 183 | 
            +
            end
         |