class OpenShift::FrontendHttpServer

Frontend Http Server

Represents the front-end HTTP server on the system.

Note: This is the Apache VirtualHost implementation; other implementations may vary.

Attributes

container_name[R]
container_uuid[R]
namespace[R]

Public Class Methods

new(container_uuid, container_name, namespace) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 90
def initialize(container_uuid, container_name, namespace)
  Syslog.open('openshift-origin-node', Syslog::LOG_PID, Syslog::LOG_LOCAL0) unless Syslog.opened?

  @config = OpenShift::Config.new
  @container_uuid = container_uuid
  @container_name = container_name
  @namespace = namespace
  @cloud_domain = @config.get("CLOUD_DOMAIN")
end

Public Instance Methods

add_alias(name) click to toggle source

Public: Add an alias to this namespace

Examples

add_alias("foo.example.com")
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 173
def add_alias(name)
  dname = clean_server_name(name)
  path = server_alias_path(dname)

  # Aliases must be globally unique across all gears.
  existing = server_alias_search(dname, true)
  if not existing.empty?
    raise FrontendHttpServerAliasException.new("Already exists", @container_uuid,                                                     @container_name, @namespace, dname )
  end

  File.open(path, "w") do |f|
    f.write("ServerAlias #{dname}")
    f.flush
  end

  create_routes_alias(dname)

  reload_all
end
add_ssl_cert(ssl_cert, priv_key, server_alias, passphrase='') click to toggle source

Public: Adds a ssl certificate for an alias

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 229
    def add_ssl_cert(ssl_cert, priv_key, server_alias, passphrase='')
      basedir = @config.get("GEAR_BASE_DIR")

      ssl_cert_name = clean_server_name(server_alias) + ".crt"
      priv_key_name = clean_server_name(server_alias) + ".key"

      app_token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
      alias_token = "#{@container_uuid}_#{@namespace}_#{server_alias}"

      app_conf_dir_path = File.join(basedir, ".httpd.d", app_token)
      alias_conf_dir_path = File.join(basedir, ".httpd.d", alias_token)
      ssl_cert_file_path = File.join(alias_conf_dir_path, ssl_cert_name)
      priv_key_file_path = File.join(alias_conf_dir_path, priv_key_name)

      # Check if the specified alias has been created for the app
      server_alias_conf_file_path = File.join(app_conf_dir_path, "server_alias-#{server_alias}.conf")
      
      unless File.exists? (server_alias_conf_file_path)
        raise FrontendHttpServerException.new("Specified alias #{server_alias} does not exist for the app",
                                              @container_uuid, @container_name,
                                              @namespace)
      end

      # Make sure that the private key is not encrypted
      if priv_key.include? "ENCRYPTED"
        raise FrontendHttpServerException.new("Private key is encrypted",
                                              @container_uuid, @container_name,
                                              @namespace)
      end

      # Create a new directory for the alias and copy the certificates
      FileUtils.mkdir_p(alias_conf_dir_path)
      File.open(ssl_cert_file_path, 'w') { |f| f.write(ssl_cert) }
      File.open(priv_key_file_path, 'w') { |f| f.write(priv_key) }

      # Verify the cert and the key
      rc, out, errout = systemu "openssl rsa -in #{priv_key_file_path}"
      if rc != 0
        FileUtils.rm_f(ssl_cert_file_path)
        FileUtils.rm_f(priv_key_file_path)
        FileUtils.rm_f(alias_conf_dir_path)
        raise FrontendHttpServerException.new("Invalid private key",
                                              @container_uuid, @container_name,
                                              @namespace)
      end

      rc, out, errout = systemu "openssl x509 -in #{ssl_cert_file_path} -text -noout"
      if rc != 0
        FileUtils.rm_f(ssl_cert_file_path)
        FileUtils.rm_f(priv_key_file_path)
        FileUtils.rm_f(alias_conf_dir_path)
        raise FrontendHttpServerException.new("Invalid ssl certificate",
                                              @container_uuid, @container_name,
                                              @namespace)
      end

      #
      # Create configuration for the alias
      #

      # Create 00000_default.conf for the alias
      default_conf_contents = "ServerName #{server_alias}
ServerAdmin openshift-bofh@redhat.com
DocumentRoot /var/www/html
DefaultType None
"

      default_conf_file_path = File.join(alias_conf_dir_path, "00000_default.conf")
      File.open(default_conf_file_path, 'w') { |f| f.write(default_conf_contents) }

      # Copy zzzzz_proxy.conf for the alias
      proxy_conf_file_src_path = File.join(app_conf_dir_path, "zzzzz_proxy.conf")
      FileUtils.cp(proxy_conf_file_src_path, alias_conf_dir_path)

      # Create top level config file for the alias
      alias_conf_contents = "<VirtualHost *:80>
  RequestHeader append X-Forwarded-Proto "http"

  Include #{alias_conf_dir_path}/*.conf
</VirtualHost>

<VirtualHost *:443>
  RequestHeader append X-Forwarded-Proto "https"

# This file gets inserted into every httpd.conf ssl section
  SSLEngine on
  
  SSLCertificateFile #{ssl_cert_file_path}
  SSLCertificateKeyFile #{priv_key_file_path}
  SSLCertificateChainFile #{ssl_cert_file_path}
  SSLCipherSuite RSA:!EXPORT:!DH:!LOW:!NULL:+MEDIUM:+HIGH
  SSLProtocol -ALL +SSLv3 +TLSv1
  SSLOptions +StdEnvVars +ExportCertData
  # SSLVerifyClient must be set for +ExportCertData to take effect, so just use
  # optional_no_ca.
  SSLVerifyClient optional_no_ca

  RequestHeader set X-Forwarded-SSL-Client-Cert %{SSL_CLIENT_CERT}e

  Include #{alias_conf_dir_path}/*.conf
</VirtualHost>
"

      alias_conf_file_path = File.join(basedir, ".httpd.d", "#{alias_token}.conf")
      File.open(alias_conf_file_path, 'w') { |f| f.write(alias_conf_contents) }

      # Remove original server alias conf file
      FileUtils.rm_f(server_alias_conf_file_path)

      # Reload httpd to pick up the new configuration
      reload_httpd
    end
clean_server_name(name) click to toggle source

Private: Validate the server name

The name is validated against DNS host name requirements from RFC 1123 and RFC 952. Additionally, OpenShift does not allow names/aliases to be an IP address.

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 384
def clean_server_name(name)
  dname = name.downcase

  if not dname.index(%r[^0-9a-z\-.]/).nil?
    raise FrontendHttpServerNameException.new("Invalid characters", @container_uuid,                                                     @container_name, @namespace, dname )
  end

  if dname.length > 255
    raise FrontendHttpServerNameException.new("Too long", @container_uuid,                                                    @container_name, @namespace, dname )
  end

  if dname.length == 0
    raise FrontendHttpServerNameException.new("Name was blank", @container_uuid,                                                    @container_name, @namespace, dname )
  end

  if dname =~ %r^\d+\.\d+\.\d+\.\d+$/
    raise FrontendHttpServerNameException.new("IP addresses are not allowed", @container_uuid,                                                    @container_name, @namespace, dname )
  end

  return dname
end
connect(path, uri, options={}) click to toggle source

Public: Connect a path element to a back-end URI for this namespace.

Examples

connect('/', 'http://127.0.250.1:8080/')
connect('/phpmyadmin, ''http://127.0.250.2:8080/')
connect('/socket, 'http:%r/127.0.250.3:8080/', {:websocket=>1}

# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 147
def connect(path, uri, options={})
  raise NotImplementedError
end
create() click to toggle source

Public: Initialize an empty configuration for this gear

Examples

create
# => nil
# directory for httpd configuration.

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 109
def create
  basedir = @config.get("GEAR_BASE_DIR")

  token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
  path = File.join(basedir, ".httpd.d", token)

  FileUtils.rm_rf(path) if File.exist?(path)
  FileUtils.mkdir_p(path)      
end
create_routes_alias(alias_name) click to toggle source

Create an alias routing file for the node web proxy server

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 436
def create_routes_alias(alias_name)
  route_file = default_routes_path
  alias_file = File.join(File.dirname(route_file), "routes_alias-#{alias_name}.json")

  begin
    File.open(route_file, 'r') do |fin|
      File.open(alias_file, 'w') do |fout|
        fout.write(fin.read.gsub("#{@container_name}-#{@namespace}.#{@cloud_domain}", "#{alias_name}"))
      end
    end
  rescue => e
    Syslog.alert("ERROR: Failure trying to create routes alias json file: #{@uuid} Exception: #{e.inspect}")
  end
end
default_routes_path() click to toggle source

Get path to the default routes.json file created for the node web proxy

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 429
def default_routes_path
  basedir = @config.get("GEAR_BASE_DIR")
  token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
  File.join(basedir, '.httpd.d', token, "routes.json")
end
destroy(async=true) click to toggle source

Public: Remove the frontend httpd configuration for a gear.

Examples

destroy
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 127
def destroy(async=true)
  basedir = @config.get("GEAR_BASE_DIR")

  path = File.join(basedir, ".httpd.d", "#{container_uuid}_*")
  FileUtils.rm_rf(Dir.glob(path))

  reload_all(async)
end
disconnect(path) click to toggle source

Public: Disconnect a path element from this namespace

Examples

disconnect('/')
disconnect('/phpmyadmin)

# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 161
def disconnect(path)
  raise NotImplementedError
end
reload_all(async=false) click to toggle source

Reload both Apache and the proxy server and combine output/return

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 452
def reload_all(async=false)
  output = ''
  errout = ''
  retc = 0

  out, err, rc = reload_node_web_proxy
  output << out
  errout << err
  retc = rc if rc != 0

  out, err, rc = reload_httpd(async)
  output << out
  errout << err
  retc = rc if rc != 0

  return output, errout, retc
end
reload_httpd(async=false) click to toggle source

Reload the Apache configuration

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 471
def reload_httpd(async=false)
  async_opt="-b" if async
  out, err, rc = shellCmd("/usr/sbin/oo-httpd-singular #{async_opt} graceful")
  Syslog.alert("ERROR: failure from oo-httpd-singular(#{rc}): #{@uuid} stdout: #{out} stderr:#{err}") unless rc == 0
  return out, err, rc
end
reload_node_web_proxy() click to toggle source

Reload the configuration of the node web proxy server

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 479
def reload_node_web_proxy
  out, err, rc = shellCmd("service openshift-node-web-proxy reload")
  Syslog.alert("ERROR: failure from openshift-node-web-proxy(#{rc}): #{@uuid} stdout: #{out} stderr:#{err}") unless rc == 0
  return out, err, rc
end
remove_alias(name) click to toggle source

Public: Removes an alias from this namespace

Examples

add_alias("foo.example.com")
# => nil

Returns nil on Success or raises on Failure

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 202
def remove_alias(name)
  dname = clean_server_name(name)
  routes_file_path = server_routes_alias_path(dname)

  FileUtils.rm_f(routes_file_path) if File.exist?(routes_file_path)

  server_alias_search(dname, false).each do |path|
    FileUtils.rm_f(path)
  end

  #
  # Cleanup for alias specific configuration
  # 
  basedir = @config.get("GEAR_BASE_DIR")
  alias_token = "#{@container_uuid}_#{@namespace}_#{dname}"

  alias_conf_dir_path = File.join(basedir, ".httpd.d", alias_token)
  alias_conf_file_path = File.join(basedir, ".httpd.d", "#{alias_token}.conf")

  # Remove alias specific configuration and certs on the gear
  FileUtils.rm_f(alias_conf_file_path)
  FileUtils.rm_rf(alias_conf_dir_path)

  reload_all
end
remove_ssl_cert(server_alias) click to toggle source

Public: Removes ssl certificate/private key associated with an alias

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 345
    def remove_ssl_cert(server_alias)
      basedir = @config.get("GEAR_BASE_DIR")
      ssl_cert_name = clean_server_name(server_alias)
      priv_key_name = clean_server_name(server_alias)

      #
      # Remove the alias specific configuration
      #
      app_token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
      alias_token = "#{@container_uuid}_#{@namespace}_#{server_alias}"

      app_conf_dir_path = File.join(basedir, ".httpd.d", app_token)
      alias_conf_dir_path = File.join(basedir, ".httpd.d", alias_token)
      #ssl_cert_file_path = File.join(alias_conf_dir_path, ssl_cert_name)
      #priv_key_file_path = File.join(alias_conf_dir_path, priv_key_name)
      alias_conf_file_path = File.join(basedir, ".httpd.d", "#{alias_token}.conf")

      #FileUtils.rm_rf(ssl_cert_file_path)
      #FileUtils.rm_rf(priv_key_file_path)
      FileUtils.rm_rf(alias_conf_file_path)
      FileUtils.rm_rf(alias_conf_dir_path)

      # Re-create original server alias conf file
      server_alias_conf_contents = "ServerAlias #{server_alias}
"

      server_alias_conf_file_path = File.join(app_conf_dir_path, "server_alias-#{server_alias}.conf")
      File.open(server_alias_conf_file_path, 'w') { |f| f.write(server_alias_conf_contents) }

      # Reload httpd to pick up the configuration changes
      reload_httpd
    end
server_alias_path(name) click to toggle source

Private: Return path to alias file

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 411
def server_alias_path(name)
  dname = clean_server_name(name)

  basedir = @config.get("GEAR_BASE_DIR")
  token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
  path = File.join(basedir, '.httpd.d', token, "server_alias-#{dname}.conf")
end
server_routes_alias_path(name) click to toggle source

Private: Return path to routes alias file name used by ws proxy server

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 420
def server_routes_alias_path(name)
  dname = clean_server_name(name)

  basedir = @config.get("GEAR_BASE_DIR")
  token = "#{@container_uuid}_#{@namespace}_#{@container_name}"
  path = File.join(basedir, '.httpd.d', token, "routes_alias-#{dname}.json")
end