summaryrefslogtreecommitdiff
path: root/provider_base
diff options
context:
space:
mode:
authorMicah <micah@leap.se>2016-05-10 14:48:26 -0400
committerMicah <micah@leap.se>2016-05-10 14:48:26 -0400
commit86c85582065c391aa13c0b9b397dfd1aa2e2ac7b (patch)
tree7c027409a517d862864bf3650f4a8a66f615162d /provider_base
parent70b1c648b94e6c007b9241a4661f33881e74485f (diff)
parent66b4c6b5ec6fe2f242020845fe92715ae2cdcc1e (diff)
Merge tag '0.8.0'
Release 0.8.0
Diffstat (limited to 'provider_base')
-rw-r--r--provider_base/common.json25
-rw-r--r--provider_base/files/service-definitions/provider.json.erb7
-rw-r--r--provider_base/lib/macros.rb15
-rw-r--r--provider_base/lib/macros/core.rb94
-rw-r--r--provider_base/lib/macros/files.rb89
-rw-r--r--provider_base/lib/macros/haproxy.rb73
-rw-r--r--provider_base/lib/macros/hosts.rb68
-rw-r--r--provider_base/lib/macros/keys.rb83
-rw-r--r--provider_base/lib/macros/nodes.rb88
-rw-r--r--provider_base/lib/macros/secrets.rb39
-rw-r--r--provider_base/lib/macros/stunnel.rb95
-rw-r--r--provider_base/provider.json5
-rw-r--r--provider_base/services/_api_tester.json13
-rw-r--r--provider_base/services/_couchdb_master.json8
-rw-r--r--provider_base/services/_couchdb_mirror.json1
-rw-r--r--provider_base/services/_couchdb_multimaster.json2
-rw-r--r--provider_base/services/couchdb.json7
-rw-r--r--provider_base/services/couchdb.rb71
-rw-r--r--provider_base/services/dns.json9
-rw-r--r--provider_base/services/monitor.json9
-rw-r--r--provider_base/services/monitor.rb3
-rw-r--r--provider_base/services/mx.json30
-rw-r--r--provider_base/services/mx.rb1
-rw-r--r--provider_base/services/openvpn.json7
-rw-r--r--provider_base/services/soledad.json13
-rw-r--r--provider_base/services/soledad.rb3
-rw-r--r--provider_base/services/static.json9
-rw-r--r--provider_base/services/webapp.json24
-rw-r--r--provider_base/tags/development.json6
-rw-r--r--provider_base/templates/common.json3
-rw-r--r--provider_base/templates/couchdb.json5
-rw-r--r--provider_base/templates/openvpn.json7
32 files changed, 170 insertions, 742 deletions
diff --git a/provider_base/common.json b/provider_base/common.json
index 3d2965d7..5e689109 100644
--- a/provider_base/common.json
+++ b/provider_base/common.json
@@ -16,6 +16,9 @@
},
"ssh": {
"authorized_keys": "= authorized_keys",
+ "config": {
+ "AllowTcpForwarding": "no"
+ },
"port": 22,
"mosh": {
"ports": "60000:61000",
@@ -47,6 +50,14 @@
"clients": {},
"servers": {}
},
+ "firewall": {
+ "ssh": {
+ "from": "sysadmin",
+ "to": "= ip_address",
+ "port": "= ssh.port"
+ },
+ "stunnel": "=> stunnel_firewall"
+ },
"platform": {
"version": "= Leap::Platform.version.to_s",
"major_version": "= Leap::Platform.major_version"
@@ -65,22 +76,22 @@
"nickserver": {
"type": "git",
"source": "https://leap.se/git/nickserver",
- "revision": "origin/master"
+ "revision": "origin/version/0.8"
+ },
+ "platform": {
+ "apt": {
+ "basic": "= 'http://deb.leap.se/' + Leap::Platform.major_version"
+ }
},
"soledad": {
"type": "apt",
"package": "soledad-server",
"revision": "latest"
},
- "tapicero": {
- "type": "git",
- "source": "https://leap.se/git/tapicero",
- "revision": "origin/version/0.7"
- },
"webapp": {
"type": "git",
"source": "https://leap.se/git/leap_web",
- "revision": "origin/version/0.7.1"
+ "revision": "origin/version/0.8"
}
}
}
diff --git a/provider_base/files/service-definitions/provider.json.erb b/provider_base/files/service-definitions/provider.json.erb
index be8ae484..a75bea61 100644
--- a/provider_base/files/service-definitions/provider.json.erb
+++ b/provider_base/files/service-definitions/provider.json.erb
@@ -6,15 +6,10 @@
)
hsh['domain'] = domain.full_suffix
- # advertise services that are 'user services' and for which there are actually nodes
- hsh['services'] ||= global.services[:service_type => :user_service].field(:name).select do |service|
- nodes_like_me[:services => service].any?
- end
-
hsh['api_version'] = "1"
hsh['api_uri'] = ["https://", api.domain, ':', api.port].join
- hsh['ca_cert_uri'] = 'https://' + webapp.domain + '/ca.crt'
+ hsh['ca_cert_uri'] = api.ca_cert_uri
hsh['ca_cert_fingerprint'] = fingerprint(:ca_cert)
hsh.dump_json
diff --git a/provider_base/lib/macros.rb b/provider_base/lib/macros.rb
deleted file mode 100644
index ecc3e6ba..00000000
--- a/provider_base/lib/macros.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# MACROS
-#
-# The methods in these files are available in the context of a .json configuration file.
-# (The module LeapCli::Macro is included in Config::Object)
-#
-
-require_relative 'macros/core'
-require_relative 'macros/files'
-require_relative 'macros/haproxy'
-require_relative 'macros/hosts'
-require_relative 'macros/keys'
-require_relative 'macros/nodes'
-require_relative 'macros/secrets'
-require_relative 'macros/stunnel'
diff --git a/provider_base/lib/macros/core.rb b/provider_base/lib/macros/core.rb
deleted file mode 100644
index 7de50f2f..00000000
--- a/provider_base/lib/macros/core.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-# encoding: utf-8
-
-module LeapCli
- module Macro
-
- #
- # return a fingerprint for a x509 certificate
- #
- def fingerprint(filename)
- "SHA256: " + X509.fingerprint("SHA256", Path.named_path(filename))
- end
-
- #
- # Creates a hash from the ssh key info in users directory, for use in
- # updating authorized_keys file. Additionally, the 'monitor' public key is
- # included, which is used by the monitor nodes to run particular commands
- # remotely.
- #
- def authorized_keys
- hash = {}
- keys = Dir.glob(Path.named_path([:user_ssh, '*']))
- keys.sort.each do |keyfile|
- ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
- name = File.basename(File.dirname(keyfile))
- until hash[name].nil?
- i ||= 1; name = "#{name}#{i+=1}"
- end
- hash[name] = {
- "type" => ssh_type,
- "key" => ssh_key
- }
- end
- ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
- hash[Leap::Platform.monitor_username] = {
- "type" => ssh_type,
- "key" => ssh_key
- }
- hash
- end
-
- def assert(assertion)
- if instance_eval(assertion)
- true
- else
- raise AssertionFailed.new(assertion), assertion, caller
- end
- end
-
- def error(msg)
- raise ConfigError.new(@node, msg), msg, caller
- end
-
- #
- # applies a JSON partial to this node
- #
- def apply_partial(partial_path)
- manager.partials(partial_path).each do |partial_data|
- self.deep_merge!(partial_data)
- end
- end
-
- #
- # If at first you don't succeed, then it is time to give up.
- #
- # try{} returns nil if anything in the block throws an exception.
- #
- # You can wrap something that might fail in `try`, like so.
- #
- # "= try{ nodes[:services => 'tor'].first.ip_address } "
- #
- def try(&block)
- yield
- rescue NoMethodError
- rescue ArgumentError
- nil
- end
-
- protected
-
- #
- # returns a node list, if argument is not already one
- #
- def listify(node_list)
- if node_list.is_a? Config::ObjectList
- node_list
- elsif node_list.is_a? Config::Object
- Config::ObjectList.new(node_list)
- else
- raise ArgumentError, 'argument must be a node or node list, not a `%s`' % node_list.class, caller
- end
- end
-
- end
-end
diff --git a/provider_base/lib/macros/files.rb b/provider_base/lib/macros/files.rb
deleted file mode 100644
index 958958bc..00000000
--- a/provider_base/lib/macros/files.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-# encoding: utf-8
-
-##
-## FILES
-##
-
-module LeapCli
- module Macro
-
- #
- # inserts the contents of a file
- #
- def file(filename, options={})
- if filename.is_a? Symbol
- filename = [filename, @node.name]
- end
- filepath = Path.find_file(filename)
- if filepath
- if filepath =~ /\.erb$/
- ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
- else
- File.read(filepath, :encoding => 'UTF-8')
- end
- else
- raise FileMissing.new(Path.named_path(filename), options)
- ""
- end
- end
-
- #
- # like #file, but allow missing files
- #
- def try_file(filename)
- return file(filename)
- rescue FileMissing
- return nil
- end
-
- #
- # returns what the file path will be, once the file is rsynced to the server.
- # an internal list of discovered file paths is saved, in order to rsync these files when needed.
- #
- # notes:
- #
- # * argument 'path' is relative to Path.provider/files or Path.provider_base/files
- # * the path returned by this method is absolute
- # * the path stored for use later by rsync is relative to Path.provider
- # * if the path does not exist locally, but exists in provider_base, then the default file from
- # provider_base is copied locally. this is required for rsync to work correctly.
- #
- def file_path(path, options={})
- if path.is_a? Symbol
- path = [path, @node.name]
- elsif path.is_a? String
- # ensure it prefixed with files/
- unless path =~ /^files\//
- path = "files/" + path
- end
- end
- actual_path = Path.find_file(path)
- if actual_path.nil?
- if options[:missing]
- raise FileMissing.new(Path.named_path(path), options)
- else
- Util::log 2, :skipping, "file_path(\"#{path}\") because there is no such file."
- end
- nil
- else
- if actual_path =~ /^#{Regexp.escape(Path.provider_base)}/
- # if file is under Path.provider_base, we must copy the default file to
- # to Path.provider in order for rsync to be able to sync the file.
- local_provider_path = actual_path.sub(/^#{Regexp.escape(Path.provider_base)}/, Path.provider)
- FileUtils.mkdir_p File.dirname(local_provider_path), :mode => 0700
- FileUtils.install actual_path, local_provider_path, :mode => 0600
- Util.log :created, Path.relative_path(local_provider_path)
- actual_path = local_provider_path
- end
- if File.directory?(actual_path) && actual_path !~ /\/$/
- actual_path += '/' # ensure directories end with /, important for building rsync command
- end
- relative_path = Path.relative_path(actual_path)
- relative_path.sub!(/^files\//, '') # remove "files/" prefix
- @node.file_paths << relative_path
- File.join(Leap::Platform.files_dir, relative_path)
- end
- end
-
- end
-end \ No newline at end of file
diff --git a/provider_base/lib/macros/haproxy.rb b/provider_base/lib/macros/haproxy.rb
deleted file mode 100644
index 602ae726..00000000
--- a/provider_base/lib/macros/haproxy.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# encoding: utf-8
-
-##
-## HAPROXY
-##
-
-module LeapCli
- module Macro
-
- #
- # creates a hash suitable for configuring haproxy. the key is the node name of the server we are proxying to.
- #
- # * node_list - a hash of nodes for the haproxy servers
- # * stunnel_client - contains the mappings to local ports for each server node.
- # * non_stunnel_port - in case self is included in node_list, the port to connect to.
- #
- # 1000 weight is used for nodes in the same location.
- # 100 otherwise.
- #
- def haproxy_servers(node_list, stunnel_clients, non_stunnel_port=nil)
- default_weight = 10
- local_weight = 100
-
- # record the hosts_file
- hostnames(node_list)
-
- # create a simple map for node name -> local stunnel accept port
- accept_ports = stunnel_clients.inject({}) do |hsh, stunnel_entry|
- name = stunnel_entry.first.sub /_[0-9]+$/, ''
- hsh[name] = stunnel_entry.last['accept_port']
- hsh
- end
-
- # if one the nodes in the node list is ourself, then there will not be a stunnel to it,
- # but we need to include it anyway in the haproxy config.
- if node_list[self.name] && non_stunnel_port
- accept_ports[self.name] = non_stunnel_port
- end
-
- # create the first pass of the servers hash
- servers = node_list.values.inject(Config::ObjectList.new) do |hsh, node|
- # make sure we have a port to talk to
- unless accept_ports[node.name]
- error "haproxy needs a local port to talk to when connecting to #{node.name}"
- end
- weight = default_weight
- try {
- weight = local_weight if self.location.name == node.location.name
- }
- hsh[node.name] = Config::Object[
- 'backup', false,
- 'host', 'localhost',
- 'port', accept_ports[node.name],
- 'weight', weight
- ]
- if node.services.include?('couchdb')
- hsh[node.name]['writable'] = node.couch.mode != 'mirror'
- end
- hsh
- end
-
- # if there are some local servers, make the others backup
- if servers.detect{|k,v| v.weight == local_weight}
- servers.each do |k,server|
- server['backup'] = server['weight'] == default_weight
- end
- end
-
- return servers
- end
-
- end
-end
diff --git a/provider_base/lib/macros/hosts.rb b/provider_base/lib/macros/hosts.rb
deleted file mode 100644
index 8281329f..00000000
--- a/provider_base/lib/macros/hosts.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# encoding: utf-8
-
-module LeapCli
- module Macro
-
- ##
- ## HOSTS
- ##
-
- #
- # records the list of hosts that are encountered for this node
- #
- def hostnames(nodes)
- @referenced_nodes ||= Config::ObjectList.new
- nodes = listify(nodes)
- nodes.each_node do |node|
- @referenced_nodes[node.name] ||= node
- end
- return nodes.values.collect {|node| node.domain.name}
- end
-
- #
- # Generates entries needed for updating /etc/hosts on a node (as a hash).
- #
- # Argument `nodes` can be nil or a list of nodes. If nil, only include the
- # IPs of the other nodes this @node as has encountered (plus all mx nodes).
- #
- # Also, for virtual machines, we use the local address if this @node is in
- # the same location as the node in question.
- #
- # We include the ssh public key for each host, so that the hash can also
- # be used to generate the /etc/ssh/known_hosts
- #
- def hosts_file(nodes=nil)
- if nodes.nil?
- if @referenced_nodes && @referenced_nodes.any?
- nodes = @referenced_nodes
- nodes = nodes.merge(nodes_like_me[:services => 'mx']) # all nodes always need to communicate with mx nodes.
- end
- end
- return {} unless nodes
- hosts = {}
- my_location = @node['location'] ? @node['location']['name'] : nil
- nodes.each_node do |node|
- hosts[node.name] = {
- 'ip_address' => node.ip_address,
- 'domain_internal' => node.domain.internal,
- 'domain_full' => node.domain.full,
- 'port' => node.ssh.port
- }
- node_location = node['location'] ? node['location']['name'] : nil
- if my_location == node_location
- if facts = @node.manager.facts[node.name]
- if facts['ec2_public_ipv4']
- hosts[node.name]['ip_address'] = facts['ec2_public_ipv4']
- end
- end
- end
- host_pub_key = Util::read_file([:node_ssh_pub_key,node.name])
- if host_pub_key
- hosts[node.name]['host_pub_key'] = host_pub_key
- end
- end
- hosts
- end
-
- end
-end \ No newline at end of file
diff --git a/provider_base/lib/macros/keys.rb b/provider_base/lib/macros/keys.rb
deleted file mode 100644
index 0ed7ccd0..00000000
--- a/provider_base/lib/macros/keys.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# encoding: utf-8
-
-#
-# Macro for dealing with cryptographic keys
-#
-
-module LeapCli
- module Macro
-
- #
- # return the path to the tor public key
- # generating key if it is missing
- #
- def tor_public_key_path(path_name, key_type)
- path = file_path(path_name)
- if path.nil?
- generate_tor_key(key_type)
- file_path(path_name)
- else
- path
- end
- end
-
- #
- # return the path to the tor private key
- # generating key if it is missing
- #
- def tor_private_key_path(path_name, key_type)
- path = file_path(path_name)
- if path.nil?
- generate_tor_key(key_type)
- file_path(path_name)
- else
- path
- end
- end
-
- #
- # Generates a onion_address from a public RSA key file.
- #
- # path_name is the named path of the Tor public key.
- #
- # Basically, an onion address is nothing more than a base32 encoding
- # of the first 10 bytes of a sha1 digest of the public key.
- #
- # Additionally, Tor ignores the 22 byte header of the public key
- # before taking the sha1 digest.
- #
- def onion_address(path_name)
- require 'base32'
- require 'base64'
- require 'openssl'
- path = Path.find_file([path_name, self.name])
- if path && File.exists?(path)
- public_key_str = File.readlines(path).grep(/^[^-]/).join
- public_key = Base64.decode64(public_key_str)
- public_key = public_key.slice(22..-1) # Tor ignores the 22 byte SPKI header
- sha1sum = Digest::SHA1.new.digest(public_key)
- Base32.encode(sha1sum.slice(0,10)).downcase
- else
- LeapCli.log :warning, 'Tor public key file "%s" does not exist' % tor_public_key_path
- end
- end
-
- private
-
- def generate_tor_key(key_type)
- if key_type == 'RSA'
- require 'certificate_authority'
- keypair = CertificateAuthority::MemoryKeyMaterial.new
- bit_size = 1024
- LeapCli.log :generating, "%s bit RSA Tor key" % bit_size do
- keypair.generate_key(bit_size)
- LeapCli::Util.write_file! [:node_tor_priv_key, self.name], keypair.private_key.to_pem
- LeapCli::Util.write_file! [:node_tor_pub_key, self.name], keypair.public_key.to_pem
- end
- else
- LeapCli.bail! 'tor.key.type of %s is not yet supported' % key_type
- end
- end
-
- end
-end
diff --git a/provider_base/lib/macros/nodes.rb b/provider_base/lib/macros/nodes.rb
deleted file mode 100644
index 8b961cbc..00000000
--- a/provider_base/lib/macros/nodes.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# encoding: utf-8
-
-##
-## node related macros
-##
-
-module LeapCli
- module Macro
-
- #
- # the list of all the nodes
- #
- def nodes
- global.nodes
- end
-
- #
- # simple alias for global.provider
- #
- def provider
- global.provider
- end
-
- #
- # returns a list of nodes that match the same environment
- #
- # if @node.environment is not set, we return other nodes
- # where environment is not set.
- #
- def nodes_like_me
- nodes[:environment => @node.environment]
- end
-
- #
- # returns a list of nodes that match the location name
- # and environment of @node.
- #
- def nodes_near_me
- if @node['location'] && @node['location']['name']
- nodes_like_me['location.name' => @node.location.name]
- else
- nodes_like_me['location' => nil]
- end
- end
-
- #
- #
- # picks a node out from the node list in such a way that:
- #
- # (1) which nodes picked which nodes is saved in secrets.json
- # (2) when other nodes call this macro with the same node list, they are guaranteed to get a different node
- # (3) if all the nodes in the pick_node list have been picked, remaining nodes are distributed randomly.
- #
- # if the node_list is empty, an exception is raised.
- # if node_list size is 1, then that node is returned and nothing is
- # memorized via the secrets.json file.
- #
- # `label` is needed to distinguish between pools of nodes for different purposes.
- #
- # TODO: more evenly balance after all the nodes have been picked.
- #
- def pick_node(label, node_list)
- if node_list.any?
- if node_list.size == 1
- return node_list.values.first
- else
- secrets_key = "pick_node(:#{label},#{node_list.keys.sort.join(',')})"
- secrets_value = @manager.secrets.retrieve(secrets_key, @node.environment) || {}
- secrets_value[@node.name] ||= begin
- node_to_pick = nil
- node_list.each_node do |node|
- next if secrets_value.values.include?(node.name)
- node_to_pick = node.name
- end
- node_to_pick ||= secrets_value.values.shuffle.first # all picked already, so pick a random one.
- node_to_pick
- end
- picked_node_name = secrets_value[@node.name]
- @manager.secrets.set(secrets_key, secrets_value, @node.environment)
- return node_list[picked_node_name]
- end
- else
- raise ArgumentError.new('pick_node(node_list): node_list cannot be empty')
- end
- end
-
- end
-end \ No newline at end of file
diff --git a/provider_base/lib/macros/secrets.rb b/provider_base/lib/macros/secrets.rb
deleted file mode 100644
index 8d1feb55..00000000
--- a/provider_base/lib/macros/secrets.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# encoding: utf-8
-
-require 'base32'
-
-module LeapCli
- module Macro
-
- #
- # inserts a named secret, generating it if needed.
- #
- # manager.export_secrets should be called later to capture any newly generated secrets.
- #
- # +length+ is the character length of the generated password.
- #
- def secret(name, length=32)
- manager.secrets.set(name, @node.environment) { Util::Secret.generate(length) }
- end
-
- # inserts a base32 encoded secret
- def base32_secret(name, length=20)
- manager.secrets.set(name, @node.environment) { Base32.encode(Util::Secret.generate(length)) }
- end
-
- # Picks a random obfsproxy port from given range
- def rand_range(name, range)
- manager.secrets.set(name, @node.environment) { rand(range) }
- end
-
- #
- # inserts an hexidecimal secret string, generating it if needed.
- #
- # +bit_length+ is the bits in the secret, (ie length of resulting hex string will be bit_length/4)
- #
- def hex_secret(name, bit_length=128)
- manager.secrets.set(name, @node.environment) { Util::Secret.generate_hex(bit_length) }
- end
-
- end
-end \ No newline at end of file
diff --git a/provider_base/lib/macros/stunnel.rb b/provider_base/lib/macros/stunnel.rb
deleted file mode 100644
index f16308c7..00000000
--- a/provider_base/lib/macros/stunnel.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-##
-## STUNNEL
-##
-
-#
-# About stunnel
-# --------------------------
-#
-# The network looks like this:
-#
-# From the client's perspective:
-#
-# |------- stunnel client --------------| |---------- stunnel server -----------------------|
-# consumer app -> localhost:accept_port -> connect:connect_port -> ??
-#
-# From the server's perspective:
-#
-# |------- stunnel client --------------| |---------- stunnel server -----------------------|
-# ?? -> *:accept_port -> localhost:connect_port -> service
-#
-
-module LeapCli
- module Macro
-
- #
- # stunnel configuration for the client side.
- #
- # +node_list+ is a ObjectList of nodes running stunnel servers.
- #
- # +port+ is the real port of the ultimate service running on the servers
- # that the client wants to connect to.
- #
- # * accept_port is the port on localhost to which local clients
- # can connect. it is auto generated serially.
- #
- # * connect_port is the port on the stunnel server to connect to.
- # it is auto generated from the +port+ argument.
- #
- # generates an entry appropriate to be passed directly to
- # create_resources(stunnel::service, hiera('..'), defaults)
- #
- # local ports are automatically generated, starting at 4000
- # and incrementing in sorted order (by node name).
- #
- def stunnel_client(node_list, port, options={})
- @next_stunnel_port ||= 4000
- node_list = listify(node_list)
- hostnames(node_list) # record the hosts
- result = Config::ObjectList.new
- node_list.each_node do |node|
- if node.name != self.name || options[:include_self]
- result["#{node.name}_#{port}"] = Config::Object[
- 'accept_port', @next_stunnel_port,
- 'connect', node.domain.internal,
- 'connect_port', stunnel_port(port),
- 'original_port', port
- ]
- @next_stunnel_port += 1
- end
- end
- result
- end
-
- #
- # generates a stunnel server entry.
- #
- # +port+ is the real port targeted service.
- #
- # * `accept_port` is the publicly bound port
- # * `connect_port` is the port that the local service is running on.
- #
- def stunnel_server(port)
- {
- "accept_port" => stunnel_port(port),
- "connect_port" => port
- }
- end
-
- private
-
- #
- # maps a real port to a stunnel port (used as the connect_port in the client config
- # and the accept_port in the server config)
- #
- def stunnel_port(port)
- port = port.to_i
- if port < 50000
- return port + 10000
- else
- return port - 10000
- end
- end
-
- end
-end \ No newline at end of file
diff --git a/provider_base/provider.json b/provider_base/provider.json
index 60ad2a9e..81b2ea98 100644
--- a/provider_base/provider.json
+++ b/provider_base/provider.json
@@ -14,6 +14,7 @@
"languages": ["en"],
"default_language": "en",
"enrollment_policy": "open",
+ "services": "= enabled_services",
"service": {
// bandwidth limit is in Bytes, storage limit is in MB.
// for example:
@@ -31,8 +32,8 @@
"bandwidth_limit": 102400,
"allow_free": "= provider.service.levels.select {|l| l['rate'].nil?}.any?",
"allow_paid": "= provider.service.levels.select {|l| !l['rate'].nil?}.any?",
- "allow_anonymous": "= provider.service.levels.select {|l| l['name'] == 'anonymous'}.any?",
- "allow_registration": "= provider.service.levels.select {|l| l['name'] != 'anonymous'}.any?",
+ "allow_anonymous": "= provider.service.levels.select {|l| l['name'] == 'anonymous'}.any? && services.include?('openvpn')",
+ "allow_registration": "= provider.enrollment_policy != 'closed' && provider.service.levels.select {|l| l['name'] != 'anonymous'}.any?",
"allow_limited_bandwidth": "= provider.service.levels.select {|l| l['bandwidth'] == 'limited'}.any?",
"allow_unlimited_bandwidth": "= provider.service.levels.select {|l| l['bandwidth'].nil?}.any?"
},
diff --git a/provider_base/services/_api_tester.json b/provider_base/services/_api_tester.json
new file mode 100644
index 00000000..790aa7d8
--- /dev/null
+++ b/provider_base/services/_api_tester.json
@@ -0,0 +1,13 @@
+//
+// This partial should be added to any service that runs tests that rely on
+// accessing the bonafide webapp API.
+//
+{
+ "testing": {
+ "monitor_auth_token": "= secret :api_monitor_auth_token",
+ "api_uri": "= global.services[:webapp].api.uri",
+ // api_hosts is not used directly, but calling hostnames() will ensure
+ // that the hostnames are added to /etc/hosts
+ "api_hosts": "= hostnames(nodes_like_me[:services => 'webapp'])"
+ }
+} \ No newline at end of file
diff --git a/provider_base/services/_couchdb_master.json b/provider_base/services/_couchdb_master.json
deleted file mode 100644
index 20c6f99b..00000000
--- a/provider_base/services/_couchdb_master.json
+++ /dev/null
@@ -1,8 +0,0 @@
-//
-// Applied to master couchdb node when there is a single master
-//
-{
- "couch": {
- "mode": "master"
- }
-} \ No newline at end of file
diff --git a/provider_base/services/_couchdb_mirror.json b/provider_base/services/_couchdb_mirror.json
index 6a3402bd..da496bae 100644
--- a/provider_base/services/_couchdb_mirror.json
+++ b/provider_base/services/_couchdb_mirror.json
@@ -1,5 +1,6 @@
//
// Applied to all non-master couchdb nodes
+// NOT CURRENTLY SUPPORTED
//
{
"stunnel": {
diff --git a/provider_base/services/_couchdb_multimaster.json b/provider_base/services/_couchdb_multimaster.json
index 0f340e00..803a9416 100644
--- a/provider_base/services/_couchdb_multimaster.json
+++ b/provider_base/services/_couchdb_multimaster.json
@@ -1,6 +1,6 @@
//
// Only applied to master couchdb nodes when there are multiple masters
-//
+// NOT CURRENTLY USED.
{
"stunnel": {
"servers": {
diff --git a/provider_base/services/couchdb.json b/provider_base/services/couchdb.json
index 8b1386f8..30cb53d1 100644
--- a/provider_base/services/couchdb.json
+++ b/provider_base/services/couchdb.json
@@ -8,8 +8,8 @@
}
},
"couch": {
- "master": false,
"port": 5984,
+ "mode": "plain",
"users": {
"admin": {
"username": "admin",
@@ -31,11 +31,6 @@
"password": "= secret :couch_soledad_password",
"salt": "= hex_secret :couch_soledad_password_salt, 128"
},
- "tapicero": {
- "username": "tapicero",
- "password": "= secret :couch_tapicero_password",
- "salt": "= hex_secret :couch_tapicero_password_salt, 128"
- },
"webapp": {
"username": "webapp",
"password": "= secret :couch_webapp_password",
diff --git a/provider_base/services/couchdb.rb b/provider_base/services/couchdb.rb
index 3bee3a67..ba7e5ae5 100644
--- a/provider_base/services/couchdb.rb
+++ b/provider_base/services/couchdb.rb
@@ -1,60 +1,27 @@
-#######################################################################
-###
-### NOTE!
-###
-### Currently, mirrors do not work! The only thing that works is all
-### nodes multimaster or a single master.
-###
-#######################################################################
#
# custom logic for couchdb json resolution
# ============================================
#
-# There are three modes for a node:
-#
-# Multimaster
-# -----------
-#
-# Multimaster uses bigcouch (soon to use couchdb in replication mode
-# similar to bigcouch).
-#
-# Use "multimaster" mode when:
-#
-# * multiple nodes are marked couch.master
-# * OR no nodes are marked couch.master
-#
-# Master
-# ------
-#
-# Master uses plain couchdb that is readable and writable.
-#
-# Use "master" mode when:
-#
-# * Exactly one node, this one, is marked as master.
-#
-# Mirror
-# ------
-#
-# Mirror creates a read-only copy of the database. It uses plain coucdhb
-# with legacy couchdb replication (http based).
-#
-# This does not currently work, because http replication can't handle
-# the number of user databases.
-#
-# Use "mirror" mode when:
-#
-# * some nodes are marked couch.master
-# * AND this node is not a master
+# bigcouch is no longer maintained, so now couchdb is required...
+# no matter what!
#
-master_count = nodes_like_me['services' => 'couchdb']['couch.master' => true].size
+if self.couch['master']
+ LeapCli::log :warning, %("The node property {couch.master:true} is deprecated.\n) +
+ %( Only {couch.mode:plain} is supported. (node #{self.name}))
+end
-if master_count == 0
- apply_partial 'services/_couchdb_multimaster.json'
-elsif couch.master && master_count > 1
- apply_partial 'services/_couchdb_multimaster.json'
-elsif couch.master && master_count == 1
- apply_partial 'services/_couchdb_master.json'
-else
- apply_partial 'services/_couchdb_mirror.json'
+couchdb_nodes = nodes_like_me['services' => 'couchdb']
+
+if couchdb_nodes.size > 1
+ LeapCli::log :error, "Having multiple nodes with {services:couchdb} is no longer supported (nodes #{couchdb_nodes.keys.join(', ')})."
+elsif self.couch.mode == "multimaster"
+ LeapCli::log :error, "Nodes with {couch.mode:multimaster} are no longer supported (node #{self.name})."
end
+
+#
+# This is needed for the "test" that creates and removes the storage db
+# for test_user_email. If that test is removed, then this is no longer
+# necessary:
+#
+apply_partial('_api_tester') \ No newline at end of file
diff --git a/provider_base/services/dns.json b/provider_base/services/dns.json
index 677d9b2c..67948ef8 100644
--- a/provider_base/services/dns.json
+++ b/provider_base/services/dns.json
@@ -3,5 +3,12 @@
"public": "= nodes['dns.public' => true].fields('domain.name', 'dns.aliases', 'ip_address')",
"private": "= nodes['dns.public' => false].fields('domain.name', 'dns.aliases', 'ip_address')"
},
- "service_type": "public_service"
+ "service_type": "public_service",
+ "firewall": {
+ "dns": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "53"
+ }
+ }
} \ No newline at end of file
diff --git a/provider_base/services/monitor.json b/provider_base/services/monitor.json
index 10d5ac81..9ddc0ec7 100644
--- a/provider_base/services/monitor.json
+++ b/provider_base/services/monitor.json
@@ -2,7 +2,7 @@
"nagios": {
"nagiosadmin_pw": "= secret :nagios_admin_password",
"domains_internal": "= nagios.hosts.values.map{|h|h['domain_internal_suffix']}.uniq",
- "environments": "= Hash[ nagios.hosts.values.map{|h|h['environment']}.uniq.map{|e| [e||'default',{'contact_emails'=>global.env(e).provider.contacts.default}]} ]",
+ "environments": "= Hash[ nagios.hosts.values.map{|h|h['environment']}.uniq.map{|e| [e||'default',{'contact_emails'=>manager.env(e).provider.contacts.default}]} ]",
"hosts": "= (self.environment == 'local' ? nodes_like_me : nodes[:environment => '!local']).pick_fields('environment', 'domain.internal', 'domain.internal_suffix', 'domain.full_suffix', 'ip_address', 'services', 'openvpn.gateway_address', 'ssh.port')"
},
"hosts": "= self.environment == 'local' ? hosts_file(nodes_like_me) : hosts_file(nodes[:environment => '!local'])",
@@ -18,5 +18,12 @@
"ca_cert": "= file :ca_cert, :missing => 'provider CA. Run `leap cert ca`'",
"client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`'",
"client_ca_key": "= file :client_ca_key, :missing => 'Certificate Authority. Run `leap cert ca`'"
+ },
+ "firewall": {
+ "monitor": {
+ "from": "sysadmin",
+ "to": "= ip_address",
+ "port": [443, 80]
+ }
}
}
diff --git a/provider_base/services/monitor.rb b/provider_base/services/monitor.rb
new file mode 100644
index 00000000..01590d5c
--- /dev/null
+++ b/provider_base/services/monitor.rb
@@ -0,0 +1,3 @@
+unless self.services.include? "webapp"
+ LeapCli.log :error, "service `monitor` requires service `webapp` on the same node (node #{self.name})."
+end
diff --git a/provider_base/services/mx.json b/provider_base/services/mx.json
index 11293ae8..c7e99d85 100644
--- a/provider_base/services/mx.json
+++ b/provider_base/services/mx.json
@@ -1,4 +1,19 @@
{
+ "mx": {
+ // provider should define their own custom aliases.
+ // these are in *addition* to the standard reserved aliases for root and postmaster, etc.
+ "aliases": {},
+ // this is the domain that is used for the OpenPGP header
+ "key_lookup_domain": "= global.services[:webapp].webapp.domain",
+ "dkim": {
+ // bit sizes larger than 2048 are not necessarily supported
+ "bit_size": 2048,
+ "public_key": "= remote_file_path(:dkim_pub_key) { generate_dkim_key(mx.dkim.bit_size) }",
+ "private_key": "= remote_file_path(:dkim_priv_key) { generate_dkim_key(mx.dkim.bit_size) }",
+ // generate selector based on first ten digits of pub key fingerprint:
+ "selector": "= fingerprint(local_file_path(:dkim_pub_key) { generate_dkim_key(mx.dkim.bit_size) }, :mode => :rsa).slice(0,10)"
+ }
+ },
"stunnel": {
"clients": {
"couch_client": "= stunnel_client(nodes_like_me[:services => :couchdb], global.services[:couchdb].couch.port)"
@@ -15,7 +30,11 @@
"password": "= secret :couch_leap_mx_password",
"salt": "= hex_secret :couch_leap_mx_password_salt, 128"
},
- "mynetworks": "= nodes['environment' => '!local'].map{|name, n| [n.ip_address, (global.facts[name]||{})['ec2_public_ipv4']]}.flatten.compact.uniq",
+ "mynetworks": "= host_ips(nodes)",
+ "rbls": ["zen.spamhaus.org"],
+ "clamav": {
+ "whitelisted_addresses": []
+ },
"x509": {
"use": true,
"use_commercial": true,
@@ -23,5 +42,12 @@
"client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`'",
"client_ca_key": "= file :client_ca_key, :missing => 'Certificate Authority. Run `leap cert ca`'"
},
- "service_type": "user_service"
+ "service_type": "user_service",
+ "firewall": {
+ "mx": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": [25, 465]
+ }
+ }
}
diff --git a/provider_base/services/mx.rb b/provider_base/services/mx.rb
new file mode 100644
index 00000000..03ee561f
--- /dev/null
+++ b/provider_base/services/mx.rb
@@ -0,0 +1 @@
+apply_partial('_api_tester')
diff --git a/provider_base/services/openvpn.json b/provider_base/services/openvpn.json
index 11cb0dc2..6f73e31c 100644
--- a/provider_base/services/openvpn.json
+++ b/provider_base/services/openvpn.json
@@ -34,5 +34,12 @@
"port" : "= rand_range('scramblesuit_port_'+name, 18000..32000)"
},
"gateway_address": "= openvpn.gateway_address"
+ },
+ "firewall": {
+ "vpn": {
+ "from": "*",
+ "to": "= openvpn.gateway_address",
+ "port": "= openvpn.ports + [obfsproxy.scramblesuit.port]"
+ }
}
}
diff --git a/provider_base/services/soledad.json b/provider_base/services/soledad.json
index ed6fbc9f..169588c8 100644
--- a/provider_base/services/soledad.json
+++ b/provider_base/services/soledad.json
@@ -1,12 +1,21 @@
{
"soledad": {
"port": 2323,
- "require_couchdb": "=> assert %(services.include? 'couchdb')",
"couchdb_soledad_user": {
"username": "= global.services[:couchdb].couch.users[:soledad].username",
"password": "= secret :couch_soledad_password",
"salt": "= hex_secret :couch_soledad_password_salt, 128"
+ },
+ "couchdb_leap_mx_user": {
+ "username": "= global.services[:couchdb].couch.users[:leap_mx].username"
}
},
- "service_type": "public_service"
+ "service_type": "public_service",
+ "firewall": {
+ "soledad": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "= soledad.port"
+ }
+ }
}
diff --git a/provider_base/services/soledad.rb b/provider_base/services/soledad.rb
new file mode 100644
index 00000000..9b220c39
--- /dev/null
+++ b/provider_base/services/soledad.rb
@@ -0,0 +1,3 @@
+unless self.services.include? "couchdb"
+ LeapCli.log :error, "service `soledad` requires service `couchdb` on the same node (node #{self.name})."
+end
diff --git a/provider_base/services/static.json b/provider_base/services/static.json
index d9f52b36..2f408ec1 100644
--- a/provider_base/services/static.json
+++ b/provider_base/services/static.json
@@ -9,5 +9,12 @@
"client_version": "= static.bootstrap_files.enabled ? provider.client_version : nil"
}
},
- "service_type": "public_service"
+ "service_type": "public_service",
+ "firewall": {
+ "static": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": [80, 443]
+ }
+ }
} \ No newline at end of file
diff --git a/provider_base/services/webapp.json b/provider_base/services/webapp.json
index 941f4f61..b1d2ca59 100644
--- a/provider_base/services/webapp.json
+++ b/provider_base/services/webapp.json
@@ -9,7 +9,7 @@
"owner", "owners", "postmaster", "reply", "robot", "ssladmin", "staff",
"support", "tech-support", "tech_support", "techsupport", "ticket",
"tickets", "vmail", "www-data"],
- "domain": "= domain.full_suffix",
+ "domain": "= provider.domain",
"modules": ["user", "billing", "help"],
"couchdb_webapp_user": "= global.services[:couchdb].couch.users[:webapp]",
"couchdb_admin_user": "= global.services[:couchdb].couch.users[:admin]",
@@ -20,7 +20,7 @@
"allow_anonymous_certs": "= provider.service.allow_anonymous",
"allow_registration": "= provider.service.allow_registration",
"default_service_level": "= provider.service.default_service_level",
- "service_levels": "= provider.service.levels",
+ "service_levels": "= service_levels()",
"secret_token": "= secret :webapp_secret_token",
"api_version": 1,
"secure": false,
@@ -31,7 +31,13 @@
},
"engines": [
"support"
- ]
+ ],
+ "locales": "= provider.languages",
+ "default_locale": "= provider.default_language",
+ "api_tokens": {
+ "monitor": "= secret :api_monitor_auth_token",
+ "allowed_ips": "= host_ips(nodes_like_me)"
+ }
},
"stunnel": {
"clients": {
@@ -53,7 +59,10 @@
"service_type": "public_service",
"api": {
"domain": "= 'api.' + webapp.domain",
- "port": 4430
+ "version": 1,
+ "port": 4430,
+ "ca_cert_uri": "= 'https://' + webapp.domain + '/ca.crt'",
+ "uri": "= %(https://#{api.domain}:#{api.port}/#{api.version})"
},
"nickserver": {
"domain": "= 'nicknym.' + domain.full_suffix",
@@ -73,5 +82,12 @@
"ca_cert": "= file :ca_cert, :missing => 'provider CA. Run `leap cert ca`'",
"client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`.'",
"client_ca_key": "= file :client_ca_key, :missing => 'Certificate Authority. Run `leap cert ca`.'"
+ },
+ "firewall": {
+ "webapp": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "= [api.port, 443, 80, nickserver.port]"
+ }
}
}
diff --git a/provider_base/tags/development.json b/provider_base/tags/development.json
index d9c2c007..caf18e9d 100644
--- a/provider_base/tags/development.json
+++ b/provider_base/tags/development.json
@@ -1,7 +1,3 @@
{
- "environment": "development",
- "domain": {
- "full_suffix": "= 'dev.' + provider.domain",
- "internal_suffix": "= 'dev.' + provider.domain_internal"
- }
+ "environment": "development"
} \ No newline at end of file
diff --git a/provider_base/templates/common.json b/provider_base/templates/common.json
new file mode 100644
index 00000000..a7675b15
--- /dev/null
+++ b/provider_base/templates/common.json
@@ -0,0 +1,3 @@
+{
+ "ip_address": "REQUIRED"
+} \ No newline at end of file
diff --git a/provider_base/templates/couchdb.json b/provider_base/templates/couchdb.json
new file mode 100644
index 00000000..34b60915
--- /dev/null
+++ b/provider_base/templates/couchdb.json
@@ -0,0 +1,5 @@
+{
+ "couch": {
+ "mode": "plain"
+ }
+}
diff --git a/provider_base/templates/openvpn.json b/provider_base/templates/openvpn.json
new file mode 100644
index 00000000..cbe183e8
--- /dev/null
+++ b/provider_base/templates/openvpn.json
@@ -0,0 +1,7 @@
+{
+ "openvpn": {
+ "gateway_address": "REQUIRED",
+ "ports": ["443"],
+ "protocols": ["tcp"]
+ }
+}