summaryrefslogtreecommitdiff
path: root/provider_base
diff options
context:
space:
mode:
Diffstat (limited to 'provider_base')
-rw-r--r--provider_base/README9
-rw-r--r--provider_base/common.json97
-rw-r--r--provider_base/files/branding/head.scss1
-rw-r--r--provider_base/files/branding/tail.scss1
-rw-r--r--provider_base/files/service-definitions/provider.json.erb16
-rw-r--r--provider_base/files/service-definitions/v1/eip-service.json.erb55
-rw-r--r--provider_base/files/service-definitions/v1/smtp-service.json.erb29
-rw-r--r--provider_base/files/service-definitions/v1/soledad-service.json.erb29
-rw-r--r--provider_base/provider.json64
-rw-r--r--provider_base/services/_api_tester.json13
-rw-r--r--provider_base/services/_couchdb_mirror.json22
-rw-r--r--provider_base/services/_couchdb_multimaster.json24
-rw-r--r--provider_base/services/couchdb.json49
-rw-r--r--provider_base/services/couchdb.rb27
-rw-r--r--provider_base/services/dns.json14
-rw-r--r--provider_base/services/monitor.json29
-rw-r--r--provider_base/services/monitor.rb3
-rw-r--r--provider_base/services/mx.json53
-rw-r--r--provider_base/services/mx.rb1
-rw-r--r--provider_base/services/obfsproxy.json9
-rw-r--r--provider_base/services/openvpn.json45
-rw-r--r--provider_base/services/soledad.json21
-rw-r--r--provider_base/services/soledad.rb3
-rw-r--r--provider_base/services/static.json20
-rw-r--r--provider_base/services/tor.json15
-rw-r--r--provider_base/services/webapp.json93
-rw-r--r--provider_base/tags/development.json3
-rw-r--r--provider_base/tags/local.json3
-rw-r--r--provider_base/tags/production.json3
-rw-r--r--provider_base/templates/common.json3
-rw-r--r--provider_base/templates/couchdb.json5
-rw-r--r--provider_base/templates/openvpn.json7
-rw-r--r--provider_base/test/openvpn/client.ovpn.erb28
33 files changed, 794 insertions, 0 deletions
diff --git a/provider_base/README b/provider_base/README
new file mode 100644
index 00000000..bb80df50
--- /dev/null
+++ b/provider_base/README
@@ -0,0 +1,9 @@
+This directory holds the base provider files that actual providers inherit from.
+
+For example:
+
+ the file........ myproject/provider/common.json
+ inherits from... myproject/leap_platform/provider_base/common.json
+
+
+
diff --git a/provider_base/common.json b/provider_base/common.json
new file mode 100644
index 00000000..5e689109
--- /dev/null
+++ b/provider_base/common.json
@@ -0,0 +1,97 @@
+{
+ "ip_address": null,
+ "environment": null,
+ "services": [],
+ "tags": [],
+ "contacts": "= provider.contacts.default",
+ "domain": {
+ "full_suffix": "= provider.domain",
+ "internal_suffix": "= provider.domain_internal",
+ "full": "= node.name + '.' + domain.full_suffix",
+ "internal": "= node.name + '.' + domain.internal_suffix",
+ "name": "= node.name + '.' + (dns.public ? domain.full_suffix : domain.internal_suffix)"
+ },
+ "dns": {
+ "public": "= service_type != 'internal_service'"
+ },
+ "ssh": {
+ "authorized_keys": "= authorized_keys",
+ "config": {
+ "AllowTcpForwarding": "no"
+ },
+ "port": 22,
+ "mosh": {
+ "ports": "60000:61000",
+ "enabled": false
+ }
+ },
+ "hosts": "=> hosts_file",
+ "x509": {
+ "use": true,
+ "use_commercial": false,
+ "cert": "= x509.use ? file(:node_x509_cert, :missing => 'x509 certificate for node $node. Run `leap cert update`') : nil",
+ "key": "= x509.use ? file(:node_x509_key, :missing => 'x509 key for node $node. Run `leap cert update`') : nil",
+ "ca_cert": "= try_file :ca_cert",
+ "commercial_cert": "= x509.use_commercial ? file([:commercial_cert, try{webapp.domain}||domain.full_suffix], :missing => 'commercial x509 certificate for node $node. Add file $file, or run `leap cert csr --domain %s` to generate a temporary self-signed cert and CSR you can use to purchase a real cert.' % (try{webapp.domain}||domain.full_suffix)) : nil",
+ "commercial_key": "= x509.use_commercial ? file([:commercial_key, try{webapp.domain}||domain.full_suffix], :missing => 'commercial x509 certificate for node $node. Add file $file, or run `leap cert csr --domain %s` to generate a temporary self-signed cert and CSR you can use to purchase a real cert.' % (try{webapp.domain}||domain.full_suffix)) : nil",
+ "commercial_ca_cert": "= x509.use_commercial ? try_file(:commercial_ca_cert) : nil"
+ },
+ "service_type": "internal_service",
+ "development": {
+ "site_config": true
+ },
+ "name": "common",
+ "location": null,
+ "enabled": true,
+ "mail": {
+ "smarthost": "= nodes_like_me[:services => :mx].exclude(self).field('domain.full')"
+ },
+ "stunnel": {
+ "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"
+ },
+ "sources": {
+ "apt": {
+ "basic": "http://httpredir.debian.org/debian/",
+ "security": "http://security.debian.org/",
+ "backports": "http://httpredir.debian.org/debian/"
+ },
+ "leap-mx": {
+ "type": "apt",
+ "package": "leap-mx",
+ "revision": "latest"
+ },
+ "nickserver": {
+ "type": "git",
+ "source": "https://leap.se/git/nickserver",
+ "revision": "origin/version/0.8"
+ },
+ "platform": {
+ "apt": {
+ "basic": "= 'http://deb.leap.se/' + Leap::Platform.major_version"
+ }
+ },
+ "soledad": {
+ "type": "apt",
+ "package": "soledad-server",
+ "revision": "latest"
+ },
+ "webapp": {
+ "type": "git",
+ "source": "https://leap.se/git/leap_web",
+ "revision": "origin/version/0.8"
+ }
+ }
+}
diff --git a/provider_base/files/branding/head.scss b/provider_base/files/branding/head.scss
new file mode 100644
index 00000000..c100a004
--- /dev/null
+++ b/provider_base/files/branding/head.scss
@@ -0,0 +1 @@
+// no head.scss set
diff --git a/provider_base/files/branding/tail.scss b/provider_base/files/branding/tail.scss
new file mode 100644
index 00000000..919aeec6
--- /dev/null
+++ b/provider_base/files/branding/tail.scss
@@ -0,0 +1 @@
+// no tail.scss set
diff --git a/provider_base/files/service-definitions/provider.json.erb b/provider_base/files/service-definitions/provider.json.erb
new file mode 100644
index 00000000..a75bea61
--- /dev/null
+++ b/provider_base/files/service-definitions/provider.json.erb
@@ -0,0 +1,16 @@
+<%=
+ # grab some fields from provider.json
+ hsh = provider.pick(
+ :languages, :description, :name, :services,
+ :enrollment_policy, :default_language, :service
+ )
+ hsh['domain'] = domain.full_suffix
+
+ hsh['api_version'] = "1"
+ hsh['api_uri'] = ["https://", api.domain, ':', api.port].join
+
+ hsh['ca_cert_uri'] = api.ca_cert_uri
+ hsh['ca_cert_fingerprint'] = fingerprint(:ca_cert)
+
+ hsh.dump_json
+%> \ No newline at end of file
diff --git a/provider_base/files/service-definitions/v1/eip-service.json.erb b/provider_base/files/service-definitions/v1/eip-service.json.erb
new file mode 100644
index 00000000..4bd220df
--- /dev/null
+++ b/provider_base/files/service-definitions/v1/eip-service.json.erb
@@ -0,0 +1,55 @@
+<%=
+ def underscore(words)
+ words = words.to_s.dup
+ words.downcase!
+ words.gsub! /[^a-z]/, '_'
+ words
+ end
+
+ def add_gateway(node, locations, options={})
+ return nil if options[:ip] == 'REQUIRED'
+ gateway = {}
+ gateway["capabilities"] = node.openvpn.pick(:ports, :protocols, :user_ips, :adblock, :filter_dns)
+ gateway["capabilities"]["transport"] = ["openvpn"]
+ gateway["host"] = node.domain.full
+ gateway["ip_address"] = options[:ip]
+ gateway["capabilities"]["limited"] = options[:limited]
+ if node['location']
+ location_name = underscore(node.location.name)
+ gateway["location"] = location_name
+ locations[location_name] ||= node.location
+ end
+ gateway
+ end
+
+ hsh = {}
+ hsh["serial"] = 1
+ hsh["version"] = 1
+ locations = {}
+ gateways = []
+ configuration = nil
+ nodes_like_me[:services => 'openvpn'].each_node do |node|
+ if node.openvpn.allow_limited && node.openvpn.allow_unlimited
+ gateways << add_gateway(node, locations, :ip => node.openvpn.gateway_address, :limited => false)
+ gateways << add_gateway(node, locations, :ip => node.openvpn.second_gateway_address, :limited => true)
+ elsif node.openvpn.allow_unlimited
+ gateways << add_gateway(node, locations, :ip => node.openvpn.gateway_address, :limited => false)
+ elsif node.openvpn.allow_limited
+ gateways << add_gateway(node, locations, :ip => node.openvpn.gateway_address, :limited => true)
+ end
+ if configuration && node.openvpn.configuration != configuration
+ log :error, "OpenVPN nodes in the environment `#{node.environment}` have conflicting `openvpn.configuration` values. This will result in bad errors."
+ end
+ configuration = node.openvpn.configuration
+ end
+ if gateways.any?
+ configuration = configuration.dup
+ if configuration['fragment'] && configuration['fragment'] == 1500
+ configuration.delete('fragment')
+ end
+ hsh["gateways"] = gateways.compact
+ hsh["locations"] = locations
+ hsh["openvpn_configuration"] = configuration
+ end
+ JSON.sorted_generate hsh
+%> \ No newline at end of file
diff --git a/provider_base/files/service-definitions/v1/smtp-service.json.erb b/provider_base/files/service-definitions/v1/smtp-service.json.erb
new file mode 100644
index 00000000..45f240ac
--- /dev/null
+++ b/provider_base/files/service-definitions/v1/smtp-service.json.erb
@@ -0,0 +1,29 @@
+<%=
+ def underscore(words)
+ words = words.to_s.dup
+ words.downcase!
+ words.gsub! /[^a-z]/, '_'
+ words
+ end
+
+ hsh = {}
+ hsh["serial"] = 1
+ hsh["version"] = 1
+ locations = {}
+ hosts = {}
+ nodes_like_me[:services => 'mx'].each_node do |node|
+ host = {}
+ host["hostname"] = node.domain.full
+ host["ip_address"] = node.ip_address
+ host["port"] = 465 # hard coded for now, later node.smtp.port
+ if node['location']
+ location_name = underscore(node.location.name)
+ host["location"] = location_name
+ locations[location_name] ||= node.location
+ end
+ hosts[node.name] = host
+ end
+ hsh["hosts"] = hosts
+ hsh["locations"] = locations
+ JSON.sorted_generate hsh
+%>
diff --git a/provider_base/files/service-definitions/v1/soledad-service.json.erb b/provider_base/files/service-definitions/v1/soledad-service.json.erb
new file mode 100644
index 00000000..0cd1c927
--- /dev/null
+++ b/provider_base/files/service-definitions/v1/soledad-service.json.erb
@@ -0,0 +1,29 @@
+<%=
+ def underscore(words)
+ words = words.to_s.dup
+ words.downcase!
+ words.gsub! /[^a-z]/, '_'
+ words
+ end
+
+ hsh = {}
+ hsh["serial"] = 1
+ hsh["version"] = 1
+ locations = {}
+ hosts = {}
+ nodes_like_me[:services => 'soledad'].each_node do |node|
+ host = {}
+ host["hostname"] = node.domain.full
+ host["ip_address"] = node.ip_address
+ host["port"] = node.soledad.port
+ if node['location']
+ location_name = underscore(node.location.name)
+ host["location"] = location_name
+ locations[location_name] ||= node.location
+ end
+ hosts[node.name] = host
+ end
+ hsh["hosts"] = hosts
+ hsh["locations"] = locations
+ JSON.sorted_generate hsh
+%> \ No newline at end of file
diff --git a/provider_base/provider.json b/provider_base/provider.json
new file mode 100644
index 00000000..81b2ea98
--- /dev/null
+++ b/provider_base/provider.json
@@ -0,0 +1,64 @@
+{
+ "domain": "REQUIRED",
+ "domain_internal": "= domain.sub(/\\.[^\\.]*$/, '.i')",
+ "name": {
+ "en": "REQUIRED"
+ },
+ "description": {
+ "en": "REQUIRED"
+ },
+ "contacts": {
+ "default": ["REQUIRED"],
+ "english": "= contacts.default.map {|email| email.split('@').join(' at the domain ')}.join(', ')"
+ },
+ "languages": ["en"],
+ "default_language": "en",
+ "enrollment_policy": "open",
+ "services": "= enabled_services",
+ "service": {
+ // bandwidth limit is in Bytes, storage limit is in MB.
+ // for example:
+ // "levels": {
+ // "1": {"name": "free", "description":"Limited service, but without cost to you.", "storage":50},
+ // "2": {"name": "basic", "description":"The standard package.", "storage":1000, "rate": {"USD":5}},
+ // "3": {"name": "pro", "description":"Extra storage for power users." , "storage":10000, "rate": {"USD":10}}
+ // }
+ "levels": {
+ "1": {
+ "name": "free", "description": "Please donate."
+ }
+ },
+ "default_service_level": 1,
+ "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? && 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?"
+ },
+ "ca": {
+ "name": "= provider.ca.organization + ' Root CA'",
+ "organization": "= provider.name[provider.default_language]",
+ "organizational_unit": "= 'https://' + provider.domain",
+ "bit_size": 4096,
+ "digest": "SHA256",
+ "life_span": "10 years",
+ "server_certificates": {
+ "bit_size": 4096,
+ "digest": "SHA256",
+ "life_span": "1 years"
+ },
+ "client_certificates": {
+ "bit_size": 2048,
+ "digest": "SHA256",
+ "life_span": "2 months",
+ "limited_prefix": "LIMITED",
+ "unlimited_prefix": "UNLIMITED"
+ }
+ },
+ "client_version": {
+ "min": "0.7",
+ "max": null
+ }
+}
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_mirror.json b/provider_base/services/_couchdb_mirror.json
new file mode 100644
index 00000000..da496bae
--- /dev/null
+++ b/provider_base/services/_couchdb_mirror.json
@@ -0,0 +1,22 @@
+//
+// Applied to all non-master couchdb nodes
+// NOT CURRENTLY SUPPORTED
+//
+{
+ "stunnel": {
+ "clients": {
+ "couch_client": "= stunnel_client(nodes[couch.replication.masters.keys], couch.port)"
+ }
+ },
+ "couch": {
+ "mode": "mirror",
+ "replication": {
+ // for now, pick the first close one, or the first one.
+ // in the future, maybe use haproxy to balance among all the masters
+ "masters": "= try{pick_node(:couch_master,nodes_near_me['services' => 'couchdb']['couch.master' => true]).pick_fields('domain.internal', 'couch.port')} || try{pick_node(:couch_master,nodes_like_me['services' => 'couchdb']['couch.master' => true]).pick_fields('domain.internal', 'couch.port')}",
+ "username": "replication",
+ "password": "= secret :couch_replication_password",
+ "role": "replication"
+ }
+ }
+}
diff --git a/provider_base/services/_couchdb_multimaster.json b/provider_base/services/_couchdb_multimaster.json
new file mode 100644
index 00000000..803a9416
--- /dev/null
+++ b/provider_base/services/_couchdb_multimaster.json
@@ -0,0 +1,24 @@
+//
+// Only applied to master couchdb nodes when there are multiple masters
+// NOT CURRENTLY USED.
+{
+ "stunnel": {
+ "servers": {
+ "epmd_server": "= stunnel_server(couch.bigcouch.epmd_port)",
+ "ednp_server": "= stunnel_server(couch.bigcouch.ednp_port)"
+ },
+ "clients": {
+ "epmd_clients": "= stunnel_client(nodes_like_me['services' => 'couchdb']['couch.mode' => 'multimaster'], couch.bigcouch.epmd_port)",
+ "ednp_clients": "= stunnel_client(nodes_like_me['services' => 'couchdb']['couch.mode' => 'multimaster'], couch.bigcouch.ednp_port)"
+ }
+ },
+ "couch": {
+ "mode": "multimaster",
+ "bigcouch": {
+ "epmd_port": 4369,
+ "ednp_port": 9002,
+ "cookie": "= secret :bigcouch_cookie",
+ "neighbors": "= nodes_like_me['services' => 'couchdb']['couch.mode' => 'multimaster'].exclude(self).field('domain.full')"
+ }
+ }
+}
diff --git a/provider_base/services/couchdb.json b/provider_base/services/couchdb.json
new file mode 100644
index 00000000..30cb53d1
--- /dev/null
+++ b/provider_base/services/couchdb.json
@@ -0,0 +1,49 @@
+{
+ "x509": {
+ "use": true
+ },
+ "stunnel": {
+ "servers": {
+ "couch_server": "= stunnel_server(couch.port)"
+ }
+ },
+ "couch": {
+ "port": 5984,
+ "mode": "plain",
+ "users": {
+ "admin": {
+ "username": "admin",
+ "password": "= secret :couch_admin_password",
+ "salt": "= hex_secret :couch_admin_password_salt, 128"
+ },
+ "leap_mx": {
+ "username": "leap_mx",
+ "password": "= secret :couch_leap_mx_password",
+ "salt": "= hex_secret :couch_leap_mx_password_salt, 128"
+ },
+ "nickserver": {
+ "username": "nickserver",
+ "password": "= secret :couch_nickserver_password",
+ "salt": "= hex_secret :couch_nickserver_password_salt, 128"
+ },
+ "soledad": {
+ "username": "soledad",
+ "password": "= secret :couch_soledad_password",
+ "salt": "= hex_secret :couch_soledad_password_salt, 128"
+ },
+ "webapp": {
+ "username": "webapp",
+ "password": "= secret :couch_webapp_password",
+ "salt": "= hex_secret :couch_webapp_password_salt, 128"
+ },
+ "replication": {
+ "username": "replication",
+ "password": "= secret :couch_replication_password",
+ "salt": "= hex_secret :couch_replication_password_salt, 128"
+ }
+ },
+ "webapp": {
+ "nagios_test_pw": "= secret :nagios_test_password"
+ }
+ }
+}
diff --git a/provider_base/services/couchdb.rb b/provider_base/services/couchdb.rb
new file mode 100644
index 00000000..ba7e5ae5
--- /dev/null
+++ b/provider_base/services/couchdb.rb
@@ -0,0 +1,27 @@
+#
+# custom logic for couchdb json resolution
+# ============================================
+#
+# bigcouch is no longer maintained, so now couchdb is required...
+# no matter what!
+#
+
+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
+
+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
new file mode 100644
index 00000000..67948ef8
--- /dev/null
+++ b/provider_base/services/dns.json
@@ -0,0 +1,14 @@
+{
+ "hosts": {
+ "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",
+ "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
new file mode 100644
index 00000000..9ddc0ec7
--- /dev/null
+++ b/provider_base/services/monitor.json
@@ -0,0 +1,29 @@
+{
+ "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'=>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'])",
+ "ssh": {
+ "monitor": {
+ "username": "= Leap::Platform.monitor_username",
+ "private_key": "= file(:monitor_priv_key)"
+ }
+ },
+ "x509": {
+ "use": true,
+ "use_commercial": true,
+ "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
new file mode 100644
index 00000000..c7e99d85
--- /dev/null
+++ b/provider_base/services/mx.json
@@ -0,0 +1,53 @@
+{
+ "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)"
+ }
+ },
+ "haproxy": {
+ "couch": {
+ "listen_port": 4096,
+ "servers": "= haproxy_servers(nodes_like_me[:services => :couchdb], stunnel.clients.couch_client, global.services[:couchdb].couch.port)"
+ }
+ },
+ "couchdb_leap_mx_user": {
+ "username": "= global.services[:couchdb].couch.users[:leap_mx].username",
+ "password": "= secret :couch_leap_mx_password",
+ "salt": "= hex_secret :couch_leap_mx_password_salt, 128"
+ },
+ "mynetworks": "= host_ips(nodes)",
+ "rbls": ["zen.spamhaus.org"],
+ "clamav": {
+ "whitelisted_addresses": []
+ },
+ "x509": {
+ "use": true,
+ "use_commercial": true,
+ "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`'"
+ },
+ "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/obfsproxy.json b/provider_base/services/obfsproxy.json
new file mode 100644
index 00000000..979d0ef9
--- /dev/null
+++ b/provider_base/services/obfsproxy.json
@@ -0,0 +1,9 @@
+{
+ "obfsproxy": {
+ "scramblesuit": {
+ "password": "= base32_secret('scramblesuit_password_'+name)",
+ "port" : "= rand_range('scramblesuit_port_'+name, 18000..32000)"
+ },
+ "gateway_address": "= try{pick_node(:obfs_gateway,nodes_near_me['services' => 'openvpn']).pick_fields('openvpn.gateway_address')} || try{pick_node(:obfs_gateway,nodes_like_me['services' => 'openvpn']).pick_fields('openvpn.gateway_address')}"
+ }
+}
diff --git a/provider_base/services/openvpn.json b/provider_base/services/openvpn.json
new file mode 100644
index 00000000..6f73e31c
--- /dev/null
+++ b/provider_base/services/openvpn.json
@@ -0,0 +1,45 @@
+{
+ "service_type": "user_service",
+ "x509": {
+ "use": true,
+ "client_ca_cert": "= file :client_ca_cert, :missing => 'Certificate Authority. Run `leap cert ca`'",
+ "dh": "= file :dh_params, :missing => 'Diffie-Hellman parameters. Run `leap cert dh`'"
+ },
+ "location": null,
+ "openvpn": {
+ "gateway_address": "REQUIRED",
+ "second_gateway_address": "= openvpn.allow_limited && openvpn.allow_unlimited ? 'REQUIRED' : nil",
+ "ports": ["80", "443", "53", "1194"],
+ "protocols": ["tcp", "udp"],
+ "filter_dns": false,
+ "adblock": false,
+ "user_ips": false,
+ "allow_limited": "= provider.service.allow_limited_bandwidth",
+ "allow_unlimited": "= provider.service.allow_unlimited_bandwidth",
+ "limited_prefix": "= provider.ca.client_certificates.limited_prefix",
+ "unlimited_prefix": "= provider.ca.client_certificates.unlimited_prefix",
+ "rate_limit": "= openvpn.allow_limited ? provider.service.bandwidth_limit : nil",
+ "configuration": {
+ "tls-cipher": "DHE-RSA-AES128-SHA",
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "keepalive": "10 30",
+ "tun-ipv6": true,
+ "fragment": 1500
+ }
+ },
+ "obfsproxy": {
+ "scramblesuit": {
+ "password": "= base32_secret('scramblesuit_password_'+name)",
+ "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
new file mode 100644
index 00000000..169588c8
--- /dev/null
+++ b/provider_base/services/soledad.json
@@ -0,0 +1,21 @@
+{
+ "soledad": {
+ "port": 2323,
+ "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",
+ "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
new file mode 100644
index 00000000..2f408ec1
--- /dev/null
+++ b/provider_base/services/static.json
@@ -0,0 +1,20 @@
+{
+ "static": {
+ "formats": "=> try{static.domains.values.collect{|d| try{d.locations.values.collect{|l|l.format}} }.flatten.compact.uniq} || []",
+ // include a copy of provider.json in case any of the configured domains happens to match provider.domain
+ "bootstrap_files": {
+ "domain": "= provider.domain",
+ "enabled": "= !! try{static.domains[provider.domain]}",
+ "provider_json": "=> static.bootstrap_files.enabled ? try{nodes_like_me[:services => 'webapp'].values.first.definition_files['provider']} : nil",
+ "client_version": "= static.bootstrap_files.enabled ? provider.client_version : nil"
+ }
+ },
+ "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/tor.json b/provider_base/services/tor.json
new file mode 100644
index 00000000..55d3d2ee
--- /dev/null
+++ b/provider_base/services/tor.json
@@ -0,0 +1,15 @@
+{
+ "tor": {
+ "bandwidth_rate": 6550,
+ "contacts": "= [provider.contacts['tor'] || provider.contacts.default].flatten",
+ "nickname": "= (self.name + secret(:tor_family)).sub('_','')[0..18]",
+ "family": "= nodes[:services => 'tor'][:environment => '!local'].field('tor.nickname').join(',')",
+ "hidden_service": {
+ "active": null,
+ "key_type": "RSA",
+ "public_key": "= tor_public_key_path(:node_tor_pub_key, tor.hidden_service.key_type) if tor.hidden_service.active",
+ "private_key": "= tor_private_key_path(:node_tor_priv_key, tor.hidden_service.key_type) if tor.hidden_service.active",
+ "address": "= onion_address(:node_tor_pub_key) if tor.hidden_service.active"
+ }
+ }
+}
diff --git a/provider_base/services/webapp.json b/provider_base/services/webapp.json
new file mode 100644
index 00000000..b1d2ca59
--- /dev/null
+++ b/provider_base/services/webapp.json
@@ -0,0 +1,93 @@
+{
+ "webapp": {
+ "admins": [],
+ "forbidden_usernames": [
+ "admin", "admins", "administrator", "administrators", "arin-admin",
+ "certmaster", "contact", "email", "help", "help-desk", "help-ticket",
+ "help-tickets", "help_desk", "help_ticket", "help_tickets", "helpdesk",
+ "helpticket", "helptickets", "info", "mail", "maildrop", "noreply",
+ "owner", "owners", "postmaster", "reply", "robot", "ssladmin", "staff",
+ "support", "tech-support", "tech_support", "techsupport", "ticket",
+ "tickets", "vmail", "www-data"],
+ "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]",
+ "customization_dir": "= file_path 'webapp'",
+ "client_certificates": "= provider.ca.client_certificates",
+ "allow_limited_certs": "= provider.service.allow_limited_bandwidth",
+ "allow_unlimited_certs": "= provider.service.allow_unlimited_bandwidth",
+ "allow_anonymous_certs": "= provider.service.allow_anonymous",
+ "allow_registration": "= provider.service.allow_registration",
+ "default_service_level": "= provider.service.default_service_level",
+ "service_levels": "= service_levels()",
+ "secret_token": "= secret :webapp_secret_token",
+ "api_version": 1,
+ "secure": false,
+ "client_version": "= provider.client_version",
+ "nagios_test_user": {
+ "username": "nagios_test",
+ "password": "= secret :nagios_test_password"
+ },
+ "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": {
+ "couch_client": "= stunnel_client(nodes_like_me[:services => :couchdb], global.services[:couchdb].couch.port)"
+ }
+ },
+ "haproxy": {
+ "couch": {
+ "listen_port": 4096,
+ "servers": "= haproxy_servers(nodes_like_me[:services => :couchdb], stunnel.clients.couch_client, global.services[:couchdb].couch.port)"
+ }
+ },
+ "definition_files": {
+ "provider": "= file :provider_json_template",
+ "eip_service": "= file [:eip_service_json_template, 'v'+webapp.api_version.to_s]",
+ "soledad_service": "= file [:soledad_service_json_template, 'v'+webapp.api_version.to_s]",
+ "smtp_service": "= file [:smtp_service_json_template, 'v'+webapp.api_version.to_s]"
+ },
+ "service_type": "public_service",
+ "api": {
+ "domain": "= 'api.' + webapp.domain",
+ "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",
+ "couchdb_nickserver_user": {
+ "username": "= global.services[:couchdb].couch.users[:nickserver].username",
+ "password": "= secret :couch_nickserver_password",
+ "salt": "= hex_secret :couch_nickserver_password_salt, 128"
+ },
+ "port": 6425
+ },
+ "dns": {
+ "aliases": "= [domain.full, webapp.domain, api.domain, nickserver.domain]"
+ },
+ "x509": {
+ "use": true,
+ "use_commercial": true,
+ "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
new file mode 100644
index 00000000..caf18e9d
--- /dev/null
+++ b/provider_base/tags/development.json
@@ -0,0 +1,3 @@
+{
+ "environment": "development"
+} \ No newline at end of file
diff --git a/provider_base/tags/local.json b/provider_base/tags/local.json
new file mode 100644
index 00000000..48312b33
--- /dev/null
+++ b/provider_base/tags/local.json
@@ -0,0 +1,3 @@
+{
+ "environment": "local"
+} \ No newline at end of file
diff --git a/provider_base/tags/production.json b/provider_base/tags/production.json
new file mode 100644
index 00000000..ea17498f
--- /dev/null
+++ b/provider_base/tags/production.json
@@ -0,0 +1,3 @@
+{
+ "environment": "production"
+} \ 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"]
+ }
+}
diff --git a/provider_base/test/openvpn/client.ovpn.erb b/provider_base/test/openvpn/client.ovpn.erb
new file mode 100644
index 00000000..af183ef4
--- /dev/null
+++ b/provider_base/test/openvpn/client.ovpn.erb
@@ -0,0 +1,28 @@
+client
+dev tun
+remote-cert-tls server
+remote-random
+nobind
+script-security 2
+verb 3
+auth SHA1
+cipher AES-128-CBC
+tls-cipher DHE-RSA-AES128-SHA
+
+<% vpn_nodes.each_node do |node| -%>
+<%= "remote #{node.openvpn.gateway_address} 1194 udp"%>
+<% end -%>
+
+<ca>
+<%= read_file! :ca_cert -%>
+</ca>
+
+<cert>
+<%# read_file! :test_client_cert -%>
+<%= cert -%>
+</cert>
+
+<key>
+<%# read_file! :test_client_key -%>
+<%= key -%>
+</key>