summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Vagrantfile5
-rw-r--r--lib/leap_cli/commands/compile.rb66
-rw-r--r--lib/leap_cli/commands/db.rb1
-rw-r--r--lib/leap_cli/macros/stunnel.rb13
-rw-r--r--provider_base/common.json13
-rw-r--r--provider_base/services/couchdb.json5
-rw-r--r--provider_base/services/dns.json9
-rw-r--r--provider_base/services/monitor.json7
-rw-r--r--provider_base/services/mx.json9
-rw-r--r--provider_base/services/openvpn.json7
-rw-r--r--provider_base/services/soledad.json9
-rw-r--r--provider_base/services/static.json9
-rw-r--r--provider_base/services/webapp.json7
-rw-r--r--puppet/manifests/site.pp1
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg2
-rw-r--r--puppet/modules/site_check_mk/files/agent/logwatch/tapicero.cfg11
-rw-r--r--puppet/modules/site_check_mk/manifests/agent/tapicero.pp26
-rw-r--r--puppet/modules/site_config/manifests/default.pp7
-rw-r--r--puppet/modules/site_config/manifests/remove.pp5
-rw-r--r--puppet/modules/site_config/manifests/remove/files.pp (renamed from puppet/modules/site_config/manifests/remove_files.pp)17
-rw-r--r--puppet/modules/site_config/manifests/remove/tapicero.pp61
-rw-r--r--puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json22
-rw-r--r--puppet/modules/site_couchdb/manifests/add_users.pp11
-rw-r--r--puppet/modules/site_couchdb/manifests/create_dbs.pp9
-rw-r--r--puppet/modules/site_couchdb/manifests/designs.pp13
-rw-r--r--puppet/modules/site_couchdb/manifests/init.pp6
-rw-r--r--puppet/modules/site_couchdb/manifests/setup.pp35
-rw-r--r--puppet/modules/site_postfix/manifests/mx/static_aliases.pp68
-rw-r--r--puppet/modules/site_postfix/templates/virtual-aliases.erb3
-rw-r--r--puppet/modules/site_webapp/templates/config.yml.erb3
-rw-r--r--puppet/modules/soledad/manifests/init.pp17
-rw-r--r--puppet/modules/soledad/manifests/server.pp21
-rw-r--r--puppet/modules/soledad/templates/soledad-server.conf.erb5
-rwxr-xr-xpuppet/modules/tapicero/files/tapicero.init60
-rw-r--r--puppet/modules/tapicero/manifests/init.pp137
-rw-r--r--puppet/modules/tapicero/templates/tapicero.yaml.erb52
-rw-r--r--tests/white-box/couchdb.rb3
-rw-r--r--tests/white-box/webapp.rb2
38 files changed, 362 insertions, 395 deletions
diff --git a/Vagrantfile b/Vagrantfile
index c9c68284..ba5451aa 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -9,6 +9,11 @@ Vagrant.configure("2") do |vagrant_config|
config.vm.box = "LEAP/wheezy"
#config.vm.network :private_network, ip: "10.5.5.102"
+
+ # forward leap_web ports
+ config.vm.network "forwarded_port", guest: 80, host:8080
+ config.vm.network "forwarded_port", guest: 443, host:4443
+
config.vm.provider "virtualbox" do |v|
v.memory = 1024
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
index 8f6c7769..c388e5c3 100644
--- a/lib/leap_cli/commands/compile.rb
+++ b/lib/leap_cli/commands/compile.rb
@@ -265,56 +265,86 @@ remove this directory if you don't use it.
# serial is any number less than 2^32 (4294967296)
#
def compile_zone_file
- provider = manager.env('default').provider
+ provider = manager.env('default').provider
hosts_seen = {}
- f = $stdout
- f.puts ZONE_HEADER % {:domain => provider.domain, :ns => provider.domain, :contact => provider.contacts.default.first.sub('@','.')}
- max_width = manager.nodes.values.inject(0) {|max, node| [max, relative_hostname(node.domain.full, provider).length].max }
- put_line = lambda do |host, line|
- host = '@' if host == ''
- f.puts("%-#{max_width}s %s" % [host, line])
- end
+ lines = []
+
+ #
+ # header
+ #
+ lines << ZONE_HEADER % {:domain => provider.domain, :ns => provider.domain, :contact => provider.contacts.default.first.sub('@','.')}
- f.puts ORIGIN_HEADER
+ #
+ # common records
+ #
+ lines << ORIGIN_HEADER
# 'A' records for primary domain
manager.nodes[:environment => '!local'].each_node do |node|
if node.dns['aliases'] && node.dns.aliases.include?(provider.domain)
- put_line.call "", "IN A #{node.ip_address}"
+ lines << ["@", "IN A #{node.ip_address}"]
end
end
-
# NS records
if provider['dns'] && provider.dns['nameservers']
provider.dns.nameservers.each do |ns|
- put_line.call "", "IN NS #{ns}."
+ lines << ["@", "IN NS #{ns}."]
end
end
- # all other records
+ # environment records
manager.environment_names.each do |env|
next if env == 'local'
nodes = manager.nodes[:environment => env]
next unless nodes.any?
- f.puts ENV_HEADER % (env.nil? ? 'default' : env)
+ spf = nil
+ lines << ENV_HEADER % (env.nil? ? 'default' : env)
nodes.each_node do |node|
if node.dns.public
- hostname = relative_hostname(node.domain.full, provider)
- put_line.call relative_hostname(node.domain.full, provider), "IN A #{node.ip_address}"
+ lines << [relative_hostname(node.domain.full, provider), "IN A #{node.ip_address}"]
end
if node.dns['aliases']
node.dns.aliases.each do |host_alias|
if host_alias != node.domain.full && host_alias != provider.domain
- put_line.call relative_hostname(host_alias, provider), "IN A #{node.ip_address}"
+ lines << [relative_hostname(host_alias, provider), "IN A #{node.ip_address}"]
end
end
end
if node.services.include? 'mx'
- put_line.call relative_hostname(node.domain.full_suffix, provider), "IN MX 10 #{relative_hostname(node.domain.full, provider)}"
+ mx_domain = relative_hostname(node.domain.full_suffix, provider)
+ lines << [mx_domain, "IN MX 10 #{relative_hostname(node.domain.full, provider)}"]
+ spf ||= [mx_domain, spf_record(node)]
end
end
+ lines << spf if spf
+ end
+
+ # print the lines
+ max_width = lines.inject(0) {|max, line| line.is_a?(Array) ? [max, line[0].length].max : max}
+ lines.each do |host, line|
+ if line.nil?
+ puts(host)
+ else
+ host = '@' if host == ''
+ puts("%-#{max_width}s %s" % [host, line])
+ end
end
end
+ #
+ # allow mail from any mx node, plus the webapp nodes.
+ #
+ # TODO: ipv6
+ #
+ def spf_record(node)
+ ips = node.nodes_like_me['services' => 'webapp'].values.collect {|n|
+ "ip4:" + n.ip_address
+ }
+ # TXT strings may not be longer than 255 characters, although
+ # you can chain multiple strings together.
+ strings = "v=spf1 MX #{ips.join(' ')} -all".scan(/.{1,255}/).join('" "')
+ %(IN TXT "#{strings}")
+ end
+
ENV_HEADER = %[
;;
;; ENVIRONMENT %s
diff --git a/lib/leap_cli/commands/db.rb b/lib/leap_cli/commands/db.rb
index e4fd3858..5703e4bd 100644
--- a/lib/leap_cli/commands/db.rb
+++ b/lib/leap_cli/commands/db.rb
@@ -40,7 +40,6 @@ module LeapCli; module Commands
def destroy_all_dbs(nodes)
ssh_connect(nodes) do |ssh|
ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "db destroyed" || echo "db already destroyed"')
- ssh.run('grep ^seq_dir /etc/leap/tapicero.yaml | cut -f2 -d\" | xargs rm -rv')
end
end
diff --git a/lib/leap_cli/macros/stunnel.rb b/lib/leap_cli/macros/stunnel.rb
index f16308c7..821bda38 100644
--- a/lib/leap_cli/macros/stunnel.rb
+++ b/lib/leap_cli/macros/stunnel.rb
@@ -49,12 +49,14 @@ module LeapCli
result = Config::ObjectList.new
node_list.each_node do |node|
if node.name != self.name || options[:include_self]
+ s_port = stunnel_port(port)
result["#{node.name}_#{port}"] = Config::Object[
'accept_port', @next_stunnel_port,
'connect', node.domain.internal,
- 'connect_port', stunnel_port(port),
+ 'connect_port', s_port,
'original_port', port
]
+ manager.connections.add(:from => @node.ip_address, :to => node.ip_address, :port => s_port)
@next_stunnel_port += 1
end
end
@@ -76,6 +78,15 @@ module LeapCli
}
end
+ #
+ # lists the ips that connect to this node, on particular ports.
+ #
+ def stunnel_firewall
+ manager.connections.select {|connection|
+ connection['to'] == @node.ip_address
+ }
+ end
+
private
#
diff --git a/provider_base/common.json b/provider_base/common.json
index e968dd27..5821789b 100644
--- a/provider_base/common.json
+++ b/provider_base/common.json
@@ -50,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"
@@ -75,11 +83,6 @@
"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",
diff --git a/provider_base/services/couchdb.json b/provider_base/services/couchdb.json
index 8b1386f8..5e65b2ec 100644
--- a/provider_base/services/couchdb.json
+++ b/provider_base/services/couchdb.json
@@ -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/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..28fb837c 100644
--- a/provider_base/services/monitor.json
+++ b/provider_base/services/monitor.json
@@ -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/mx.json b/provider_base/services/mx.json
index 4d1b3dad..d6e9fff9 100644
--- a/provider_base/services/mx.json
+++ b/provider_base/services/mx.json
@@ -31,5 +31,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/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..76f7155f 100644
--- a/provider_base/services/soledad.json
+++ b/provider_base/services/soledad.json
@@ -8,5 +8,12 @@
"salt": "= hex_secret :couch_soledad_password_salt, 128"
}
},
- "service_type": "public_service"
+ "service_type": "public_service",
+ "firewall": {
+ "soledad": {
+ "from": "*",
+ "to": "= ip_address",
+ "port": "= soledad.port"
+ }
+ }
}
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 039b1c0b..9e3d751b 100644
--- a/provider_base/services/webapp.json
+++ b/provider_base/services/webapp.json
@@ -76,5 +76,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/puppet/manifests/site.pp b/puppet/manifests/site.pp
index 912234ac..91dd2d3c 100644
--- a/puppet/manifests/site.pp
+++ b/puppet/manifests/site.pp
@@ -20,7 +20,6 @@ if member($services, 'openvpn') {
if member($services, 'couchdb') {
include site_couchdb
- include tapicero
}
if member($services, 'webapp') {
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
index 95ddd2ca..0f378a5a 100644
--- a/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
+++ b/puppet/modules/site_check_mk/files/agent/logwatch/bigcouch.cfg
@@ -6,7 +6,7 @@
I 127.0.0.1 localhost:5984 .* ok
# https://leap.se/code/issues/5246
I Shutting down group server
- # ignore bigcouch conflict errors, mainly coming from tapicero creating new users
+ # ignore bigcouch conflict errors
I Error in process.*{{nocatch,conflict}
# ignore "Uncaught error in HTTP request: {exit, normal}" error
# it's suppressed in later versions of bigcouch anhow
diff --git a/puppet/modules/site_check_mk/files/agent/logwatch/tapicero.cfg b/puppet/modules/site_check_mk/files/agent/logwatch/tapicero.cfg
deleted file mode 100644
index d98f5094..00000000
--- a/puppet/modules/site_check_mk/files/agent/logwatch/tapicero.cfg
+++ /dev/null
@@ -1,11 +0,0 @@
-/var/log/leap/tapicero.log
-# Ignore transient Tapicero errors when creating a db (#6511)
- I tapicero.*(Creating database|Checking security of|Writing security to|Uploading design doc to) user-.* failed (\(trying again soon\)|(twice )?due to): (RestClient::ResourceNotFound|RestClient::InternalServerError): (404 Resource Not Found|500 Internal Server Error)
- C tapicero.*RestClient::InternalServerError:
-# possible race condition between multiple tapicero
-# instances, so we ignore it
-# see https://leap.se/code/issues/5168
- I tapicero.*RestClient::PreconditionFailed:
- C tapicero.*Creating database.*failed due to:
- C tapicero.*failed
- W tapicero.*Couch stream ended unexpectedly.
diff --git a/puppet/modules/site_check_mk/manifests/agent/tapicero.pp b/puppet/modules/site_check_mk/manifests/agent/tapicero.pp
deleted file mode 100644
index 8505b34a..00000000
--- a/puppet/modules/site_check_mk/manifests/agent/tapicero.pp
+++ /dev/null
@@ -1,26 +0,0 @@
-# sets up tapicero monitoring
-class site_check_mk::agent::tapicero {
-
- include ::site_nagios::plugins
-
- # watch logs
- file { '/etc/check_mk/logwatch.d/tapicero.cfg':
- source => 'puppet:///modules/site_check_mk/agent/logwatch/tapicero.cfg',
- }
-
- # local nagios plugin checks via mrpe
- augeas {
- 'Tapicero_Procs':
- incl => '/etc/check_mk/mrpe.cfg',
- lens => 'Spacevars.lns',
- changes => [
- 'rm /files/etc/check_mk/mrpe.cfg/Tapicero_Procs',
- "set Tapicero_Procs \"/usr/lib/nagios/plugins/check_procs -w 1:1 -c 1:1 --ereg-argument-array='^tapicero$'\"" ],
- require => File['/etc/check_mk/mrpe.cfg'];
- 'Tapicero_Heartbeat':
- incl => '/etc/check_mk/mrpe.cfg',
- lens => 'Spacevars.lns',
- changes => 'set Tapicero_Heartbeat \'/usr/local/lib/nagios/plugins/check_last_regex_in_log -f /var/log/leap/tapicero.log -r "tapicero" -w 1200 -c 2400\'',
- require => File['/etc/check_mk/mrpe.cfg'];
- }
-}
diff --git a/puppet/modules/site_config/manifests/default.pp b/puppet/modules/site_config/manifests/default.pp
index e69e4b7b..6b10dc19 100644
--- a/puppet/modules/site_config/manifests/default.pp
+++ b/puppet/modules/site_config/manifests/default.pp
@@ -1,3 +1,4 @@
+# common things to set up on every node
class site_config::default {
tag 'leap_base'
@@ -29,7 +30,7 @@ class site_config::default {
# i.e. openstack/aws nodes, vagrant nodes
# fix dhclient from changing resolver information
- if $::dhcp_enabled == 'true' {
+ if $::dhcp_enabled == 'true' {
include site_config::dhclient
}
@@ -58,7 +59,9 @@ class site_config::default {
# set up core leap files and directories
include site_config::files
- include site_config::remove_files
+
+ # remove leftovers from previous deploys
+ include site_config::remove
if ! member($services, 'mx') {
include site_postfix::satellite
diff --git a/puppet/modules/site_config/manifests/remove.pp b/puppet/modules/site_config/manifests/remove.pp
new file mode 100644
index 00000000..00502c0a
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove.pp
@@ -0,0 +1,5 @@
+# remove leftovers from previous deploys
+class site_config::remove {
+ include site_config::remove::files
+ include site_config::remove::tapicero
+}
diff --git a/puppet/modules/site_config/manifests/remove_files.pp b/puppet/modules/site_config/manifests/remove/files.pp
index 51d1ea88..66647d31 100644
--- a/puppet/modules/site_config/manifests/remove_files.pp
+++ b/puppet/modules/site_config/manifests/remove/files.pp
@@ -9,7 +9,17 @@
# release.
#
-class site_config::remove_files {
+class site_config::remove::files {
+
+ #
+ # Platform 0.8 removals
+ #
+
+ tidy {
+ '/etc/apache/sites-enabled/leap_webapp.conf':
+ notify => Service['apache'];
+ }
+
#
# Platform 0.7 removals
@@ -27,14 +37,11 @@ class site_config::remove_files {
path => '/var/log/',
recurse => true,
matches => 'leap_mx*';
- 'leap_mx_rotate':
- path => '/var/log/leap/',
- recurse => true,
- matches => [ 'mx.log.[0-9]', 'mx.log.[0-9]?', 'mx.log.[6-9]?gz'];
'/srv/leap/webapp/public/provider.json':;
'/srv/leap/couchdb/designs/tmp_users':
recurse => true,
rmdirs => true;
+ '/etc/leap/soledad-server.conf':;
}
if member($::services, 'webapp') {
diff --git a/puppet/modules/site_config/manifests/remove/tapicero.pp b/puppet/modules/site_config/manifests/remove/tapicero.pp
new file mode 100644
index 00000000..497cf8b2
--- /dev/null
+++ b/puppet/modules/site_config/manifests/remove/tapicero.pp
@@ -0,0 +1,61 @@
+# remove tapicero leftovers from previous deploys
+class site_config::remove::tapicero {
+
+ exec { 'kill_tapicero':
+ onlyif => '/usr/bin/test -s /var/run/tapicero.pid',
+ command => '/usr/bin/pkill --pidfile /var/run/tapicero.pid'
+ }
+
+ user { 'tapicero':
+ ensure => absent;
+ }
+
+ group { 'tapicero':
+ ensure => absent,
+ require => User['tapicero'];
+ }
+
+ tidy {
+ '/srv/leap/tapicero':
+ recurse => true,
+ require => [ Exec['kill_tapicero'] ];
+ '/var/lib/leap/tapicero':
+ require => [ Exec['kill_tapicero'] ];
+ '/var/run/tapicero':
+ require => [ Exec['kill_tapicero'] ];
+ '/etc/leap/tapicero.yaml':
+ require => [ Exec['kill_tapicero'] ];
+ '/etc/init.d/tapicero':
+ require => [ Exec['kill_tapicero'] ];
+ 'tapicero_logs':
+ path => '/var/log/leap',
+ recurse => true,
+ matches => 'tapicero*',
+ require => [ Exec['kill_tapicero'] ];
+ '/etc/check_mk/logwatch.d/tapicero.cfg':
+ notify => Exec['check_mk-refresh'];
+ 'checkmk_logwatch_spool':
+ path => '/var/lib/check_mk/logwatch',
+ recurse => true,
+ matches => '*tapicero.log',
+ require => Exec['kill_tapicero'],
+ notify => Exec['check_mk-refresh'];
+ }
+
+ # remove local nagios plugin checks via mrpe
+ augeas {
+ 'Tapicero_Procs':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm /files/etc/check_mk/mrpe.cfg/Tapicero_Procs',
+ require => File['/etc/check_mk/mrpe.cfg'],
+ notify => Exec['check_mk-refresh'];
+ 'Tapicero_Heartbeat':
+ incl => '/etc/check_mk/mrpe.cfg',
+ lens => 'Spacevars.lns',
+ changes => 'rm Tapicero_Heartbeat',
+ require => File['/etc/check_mk/mrpe.cfg'],
+ notify => Exec['check_mk-refresh'];
+ }
+
+}
diff --git a/puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json b/puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json
new file mode 100644
index 00000000..006c1ea1
--- /dev/null
+++ b/puppet/modules/site_couchdb/files/designs/invite_codes/InviteCode.json
@@ -0,0 +1,22 @@
+{
+ "_id": "_design/InviteCode",
+ "language": "javascript",
+ "views": {
+ "by__id": {
+ "map": " function(doc) {\n if ((doc['type'] == 'InviteCode') && (doc['_id'] != null)) {\n emit(doc['_id'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_invite_code": {
+ "map": " function(doc) {\n if ((doc['type'] == 'InviteCode') && (doc['invite_code'] != null)) {\n emit(doc['invite_code'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "by_invite_count": {
+ "map": " function(doc) {\n if ((doc['type'] == 'InviteCode') && (doc['invite_count'] != null)) {\n emit(doc['invite_count'], 1);\n }\n }\n",
+ "reduce": "_sum"
+ },
+ "all": {
+ "map": " function(doc) {\n if (doc['type'] == 'InviteCode') {\n emit(doc._id, null);\n }\n }\n"
+ }
+ },
+ "couchrest-hash": "83fb8f504520b4a9c7ddbb7928cd0ce3"
+} \ No newline at end of file
diff --git a/puppet/modules/site_couchdb/manifests/add_users.pp b/puppet/modules/site_couchdb/manifests/add_users.pp
index 2f734ed4..c905316b 100644
--- a/puppet/modules/site_couchdb/manifests/add_users.pp
+++ b/puppet/modules/site_couchdb/manifests/add_users.pp
@@ -1,3 +1,4 @@
+# add couchdb users for all services
class site_couchdb::add_users {
Class['site_couchdb::create_dbs']
@@ -35,16 +36,6 @@ class site_couchdb::add_users {
require => Couchdb::Query::Setup['localhost']
}
- ### tapicero couchdb user
- ### admin: needs to be able to create user-<uuid> databases
- ### read: users
- couchdb::add_user { $site_couchdb::couchdb_tapicero_user:
- roles => '["users"]',
- pw => $site_couchdb::couchdb_tapicero_pw,
- salt => $site_couchdb::couchdb_tapicero_salt,
- require => Couchdb::Query::Setup['localhost']
- }
-
## webapp couchdb user
## read/write: users, tokens, sessions, tickets, identities, customer
couchdb::add_user { $site_couchdb::couchdb_webapp_user:
diff --git a/puppet/modules/site_couchdb/manifests/create_dbs.pp b/puppet/modules/site_couchdb/manifests/create_dbs.pp
index eea4bbf5..a2d1c655 100644
--- a/puppet/modules/site_couchdb/manifests/create_dbs.pp
+++ b/puppet/modules/site_couchdb/manifests/create_dbs.pp
@@ -90,4 +90,13 @@ class site_couchdb::create_dbs {
members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
require => Couchdb::Query::Setup['localhost']
}
+
+ ## invite_codes db
+ ## store invite codes for new signups
+ ## r/w: webapp
+ couchdb::create_db { 'invite_codes':
+ members => "{ \"names\": [\"${site_couchdb::couchdb_webapp_user}\"], \"roles\": [\"replication\"] }",
+ require => Couchdb::Query::Setup['localhost']
+ }
+
}
diff --git a/puppet/modules/site_couchdb/manifests/designs.pp b/puppet/modules/site_couchdb/manifests/designs.pp
index 1ab1c6a1..e5fd94c6 100644
--- a/puppet/modules/site_couchdb/manifests/designs.pp
+++ b/puppet/modules/site_couchdb/manifests/designs.pp
@@ -12,12 +12,13 @@ class site_couchdb::designs {
}
site_couchdb::upload_design {
- 'customers': design => 'customers/Customer.json';
- 'identities': design => 'identities/Identity.json';
- 'tickets': design => 'tickets/Ticket.json';
- 'messages': design => 'messages/Message.json';
- 'users': design => 'users/User.json';
- 'tmp_users': design => 'users/User.json';
+ 'customers': design => 'customers/Customer.json';
+ 'identities': design => 'identities/Identity.json';
+ 'tickets': design => 'tickets/Ticket.json';
+ 'messages': design => 'messages/Message.json';
+ 'users': design => 'users/User.json';
+ 'tmp_users': design => 'users/User.json';
+ 'invite_codes': design => 'invite_codes/InviteCode.json';
'shared_docs':
db => 'shared',
design => 'shared/docs.json';
diff --git a/puppet/modules/site_couchdb/manifests/init.pp b/puppet/modules/site_couchdb/manifests/init.pp
index 6b6ddd3a..1ec15f00 100644
--- a/puppet/modules/site_couchdb/manifests/init.pp
+++ b/puppet/modules/site_couchdb/manifests/init.pp
@@ -26,11 +26,6 @@ class site_couchdb {
$couchdb_soledad_pw = $couchdb_soledad['password']
$couchdb_soledad_salt = $couchdb_soledad['salt']
- $couchdb_tapicero = $couchdb_users['tapicero']
- $couchdb_tapicero_user = $couchdb_tapicero['username']
- $couchdb_tapicero_pw = $couchdb_tapicero['password']
- $couchdb_tapicero_salt = $couchdb_tapicero['salt']
-
$couchdb_webapp = $couchdb_users['webapp']
$couchdb_webapp_user = $couchdb_webapp['username']
$couchdb_webapp_pw = $couchdb_webapp['password']
@@ -66,6 +61,5 @@ class site_couchdb {
if $couchdb_backup { include site_couchdb::backup }
include site_check_mk::agent::couchdb
- include site_check_mk::agent::tapicero
}
diff --git a/puppet/modules/site_couchdb/manifests/setup.pp b/puppet/modules/site_couchdb/manifests/setup.pp
index 69bd1c6a..fef48505 100644
--- a/puppet/modules/site_couchdb/manifests/setup.pp
+++ b/puppet/modules/site_couchdb/manifests/setup.pp
@@ -12,27 +12,40 @@ class site_couchdb::setup {
$user = $site_couchdb::couchdb_admin_user
- # /etc/couchdb/couchdb-admin.netrc is deployed by couchdb::query::setup
- # we symlink to couchdb.netrc for puppet commands.
- # we symlink this to /root/.netrc for couchdb_scripts (eg. backup)
- # and makes life easier for the admin (i.e. using curl/wget without
- # passing credentials)
+ # setup /etc/couchdb/couchdb-admin.netrc for couchdb admin access
+ couchdb::query::setup { 'localhost':
+ user => $user,
+ pw => $site_couchdb::couchdb_admin_pw
+ }
+
+ # We symlink /etc/couchdb/couchdb-admin.netrc to /etc/couchdb/couchdb.netrc
+ # for puppet commands, and to to /root/.netrc for couchdb_scripts
+ # (eg. backup) and to makes life easier for the admin on the command line
+ # (i.e. using curl/wget without passing credentials)
file {
'/etc/couchdb/couchdb.netrc':
ensure => link,
target => "/etc/couchdb/couchdb-${user}.netrc";
-
'/root/.netrc':
ensure => link,
target => '/etc/couchdb/couchdb.netrc';
+ }
- '/srv/leap/couchdb':
- ensure => directory
+ # setup /etc/couchdb/couchdb-soledad-admin.netrc file for couchdb admin
+ # access, accessible only for the soledad-admin user to create soledad
+ # userdbs
+ file { '/etc/couchdb/couchdb-soledad-admin.netrc':
+ content => "machine localhost login ${user} password ${site_couchdb::couchdb_admin_pw}",
+ mode => '0400',
+ owner => 'soledad-admin',
+ group => 'root',
+ require => [ Package['couchdb'], User['soledad-admin'] ];
}
- couchdb::query::setup { 'localhost':
- user => $user,
- pw => $site_couchdb::couchdb_admin_pw,
+ # Checkout couchdb_scripts repo
+ file {
+ '/srv/leap/couchdb':
+ ensure => directory
}
vcsrepo { '/srv/leap/couchdb/scripts':
diff --git a/puppet/modules/site_postfix/manifests/mx/static_aliases.pp b/puppet/modules/site_postfix/manifests/mx/static_aliases.pp
index e9118470..71c0555a 100644
--- a/puppet/modules/site_postfix/manifests/mx/static_aliases.pp
+++ b/puppet/modules/site_postfix/manifests/mx/static_aliases.pp
@@ -1,37 +1,75 @@
#
# Defines static, hard coded aliases that are not in the database.
+# These aliases take precedence over the database aliases.
+#
+# There are three classes of reserved names:
+#
+# (1) forbidden_usernames:
+# Some usernames are forbidden and cannot be registered.
+# this is defined in node property webapp.forbidden_usernames
+# This is enforced by the webapp.
+#
+# (2) public aliases:
+# Some aliases for root, and are publicly exposed so that anyone
+# can deliver mail to them. For example, postmaster.
+# These are implemented in the virtual alias map, which takes
+# precedence over the local alias map.
+#
+# (3) local aliases:
+# Some aliases are only available locally: mail can be delivered
+# to the alias if the mail originates from the local host, or is
+# hostname qualified, but otherwise it will be rejected.
+# These are implemented in the local alias map.
+#
+# The alias for local 'root' is defined elsewhere. In this file, we
+# define the virtual 'root@domain' (which can be overwritten by
+# defining an entry for root in node property mx.aliases).
#
class site_postfix::mx::static_aliases {
$mx = hiera('mx')
- $aliases = $mx['aliases']
+ $root_recipients = hiera('contacts')
#
- # Predefined aliases.
- #
- # Defines which mail addresses shouldn't be available and where they should
- # fwd
- #
- # TODO: reconcile this with the node property webapp.forbidden_usernames
+ # LOCAL ALIASES
#
+
# NOTE: if you remove one of these, they will still appear in the
# /etc/aliases file
- #
+ $local_aliases = [
+ 'admin', 'administrator', 'bin', 'cron', 'games', 'ftp', 'lp', 'maildrop',
+ 'mysql', 'news', 'nobody', 'noc', 'postgresql', 'ssladmin', 'sys',
+ 'usenet', 'uucp', 'www', 'www-data'
+ ]
+
postfix::mailalias {
- [ 'abuse', 'admin', 'arin-admin', 'administrator', 'bin', 'cron',
- 'certmaster', 'domainadmin', 'games', 'ftp', 'hostmaster', 'lp',
- 'maildrop', 'mysql', 'news', 'nobody', 'noc', 'postmaster', 'postgresql',
- 'security', 'ssladmin', 'sys', 'usenet', 'uucp', 'webmaster', 'www',
- 'www-data',
- ]:
+ $local_aliases:
ensure => present,
recipient => 'root'
}
#
- # Custom static virtual aliases.
+ # PUBLIC ALIASES
#
+
+ $public_aliases = $mx['aliases']
+
+ $default_public_aliases = {
+ 'root' => $root_recipients,
+ 'abuse' => 'postmaster',
+ 'arin-admin' => 'root',
+ 'certmaster' => 'hostmaster',
+ 'domainadmin' => 'hostmaster',
+ 'hostmaster' => 'root',
+ 'mailer-daemon' => 'postmaster',
+ 'postmaster' => 'root',
+ 'security' => 'root',
+ 'webmaster' => 'hostmaster',
+ }
+
+ $aliases = merge($default_public_aliases, $public_aliases)
+
exec { 'postmap_virtual_aliases':
command => '/usr/sbin/postmap /etc/postfix/virtual-aliases',
refreshonly => true,
diff --git a/puppet/modules/site_postfix/templates/virtual-aliases.erb b/puppet/modules/site_postfix/templates/virtual-aliases.erb
index c474e734..8373de97 100644
--- a/puppet/modules/site_postfix/templates/virtual-aliases.erb
+++ b/puppet/modules/site_postfix/templates/virtual-aliases.erb
@@ -1,8 +1,7 @@
#
# This file is managed by puppet.
#
-# This is a map of custom, non-standard aliases. The contents of this file
-# are derived from the node property `mx.aliases`.
+# These virtual aliases take precedence over all other aliases.
#
#
diff --git a/puppet/modules/site_webapp/templates/config.yml.erb b/puppet/modules/site_webapp/templates/config.yml.erb
index 5cb436fc..19ed6b7b 100644
--- a/puppet/modules/site_webapp/templates/config.yml.erb
+++ b/puppet/modules/site_webapp/templates/config.yml.erb
@@ -21,7 +21,8 @@ production = {
"default_service_level" => @webapp['default_service_level'],
"service_levels" => @webapp['service_levels'],
"allow_registration" => @webapp['allow_registration'],
- "handle_blacklist" => @webapp['forbidden_usernames']
+ "handle_blacklist" => @webapp['forbidden_usernames'],
+ "invite_required" => @webapp['invite_required']
}
if @webapp['engines'] && @webapp['engines'].any?
diff --git a/puppet/modules/soledad/manifests/init.pp b/puppet/modules/soledad/manifests/init.pp
index 7cf0b729..6a2c328e 100644
--- a/puppet/modules/soledad/manifests/init.pp
+++ b/puppet/modules/soledad/manifests/init.pp
@@ -1,18 +1,29 @@
+# set up users, group and directories for soledad-server
+# although the soledad users are already created by the
+# soledad-server package
class soledad {
group { 'soledad':
- ensure => present,
- allowdupe => false;
+ ensure => present,
+ system => true,
}
user { 'soledad':
ensure => present,
- allowdupe => false,
+ system => true,
gid => 'soledad',
home => '/srv/leap/soledad',
require => Group['soledad'];
}
+ user { 'soledad-admin':
+ ensure => present,
+ system => true,
+ gid => 'soledad',
+ home => '/srv/leap/soledad',
+ require => Group['soledad'];
+ }
+
file {
'/srv/leap/soledad':
ensure => directory,
diff --git a/puppet/modules/soledad/manifests/server.pp b/puppet/modules/soledad/manifests/server.pp
index b71fab69..e437c8f2 100644
--- a/puppet/modules/soledad/manifests/server.pp
+++ b/puppet/modules/soledad/manifests/server.pp
@@ -1,3 +1,4 @@
+# setup soledad-server
class soledad::server {
tag 'leap_service'
include soledad
@@ -22,13 +23,19 @@ class soledad::server {
# SOLEDAD CONFIG
#
- file { '/etc/leap/soledad-server.conf':
- content => template('soledad/soledad-server.conf.erb'),
- owner => 'soledad',
- group => 'soledad',
- mode => '0600',
- notify => Service['soledad-server'],
- require => Class['soledad'];
+ file {
+ '/etc/soledad':
+ ensure => directory,
+ owner => 'root',
+ group => 'root',
+ mode => '0755';
+ '/etc/soledad/soledad-server.conf':
+ content => template('soledad/soledad-server.conf.erb'),
+ owner => 'soledad',
+ group => 'soledad',
+ mode => '0640',
+ notify => Service['soledad-server'],
+ require => Class['soledad'];
}
package { $sources['soledad']['package']:
diff --git a/puppet/modules/soledad/templates/soledad-server.conf.erb b/puppet/modules/soledad/templates/soledad-server.conf.erb
index 47d1f6e4..42cf44d8 100644
--- a/puppet/modules/soledad/templates/soledad-server.conf.erb
+++ b/puppet/modules/soledad/templates/soledad-server.conf.erb
@@ -1,3 +1,4 @@
[soledad-server]
-couch_url = http://<%= @couchdb_user %>:<%= @couchdb_password %>@<%= @couchdb_host %>:<%= @couchdb_port %>
-
+couch_url = http://<%= @couchdb_user %>:<%= @couchdb_password %>@<%= @couchdb_host %>:<%= @couchdb_port %>
+create_cmd = sudo -u soledad-admin /usr/bin/create-user-db
+admin_netrc = /etc/couchdb/couchdb-soledad-admin.netrc
diff --git a/puppet/modules/tapicero/files/tapicero.init b/puppet/modules/tapicero/files/tapicero.init
deleted file mode 100755
index 7a9af45f..00000000
--- a/puppet/modules/tapicero/files/tapicero.init
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/bin/sh
-
-### BEGIN INIT INFO
-# Provides: tapicero
-# Required-Start: $remote_fs $syslog
-# Required-Stop: $remote_fs $syslog
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: tapicero initscript
-# Description: Controls tapicero daemon
-### END INIT INFO
-
-PATH=/sbin:/usr/sbin:/bin:/usr/bin
-BUNDLER=/usr/bin/bundle
-NAME=tapicero
-HOME="/srv/leap"
-DAEMON="${HOME}/${NAME}/bin/${NAME}"
-BUNDLE_GEMFILE="${HOME}/${NAME}/Gemfile"
-
-export BUNDLE_GEMFILE
-
-# exit if the daemon doesn't exist
-[ -x "$DAEMON" ] || exit 0
-
-. /lib/init/vars.sh
-. /lib/lsb/init-functions
-
-if [ "$VERBOSE" != no ]; then
- OPTIONS="--verbose"
-else
- OPTIONS=""
-fi
-
-case "$1" in
- start)
- $BUNDLER exec $DAEMON start $OPTIONS
- exit $?
- ;;
- stop)
- $BUNDLER exec $DAEMON stop $OPTIONS
- exit $?
- ;;
- restart)
- $BUNDLER exec $DAEMON restart $OPTIONS
- exit $?
- ;;
- reload)
- $BUNDLER exec $DAEMON reload $OPTIONS
- exit $?
- ;;
- status)
- $BUNDLER exec $DAEMON status $OPTIONS
- exit $?
- ;;
- *)
- echo "Usage: /etc/init.d/$NAME {start|stop|reload|restart|status}"
- exit 1
-esac
-
-exit 0
diff --git a/puppet/modules/tapicero/manifests/init.pp b/puppet/modules/tapicero/manifests/init.pp
deleted file mode 100644
index ca8488c8..00000000
--- a/puppet/modules/tapicero/manifests/init.pp
+++ /dev/null
@@ -1,137 +0,0 @@
-class tapicero {
- tag 'leap_service'
-
- $couchdb = hiera('couch')
- $couchdb_port = $couchdb['port']
-
- $couchdb_users = $couchdb['users']
-
- $couchdb_admin_user = $couchdb_users['admin']['username']
- $couchdb_admin_password = $couchdb_users['admin']['password']
-
- $couchdb_soledad_user = $couchdb_users['soledad']['username']
- $couchdb_leap_mx_user = $couchdb_users['leap_mx']['username']
-
- $couchdb_mode = $couchdb['mode']
- $couchdb_replication = $couchdb['replication']
-
- $sources = hiera('sources')
-
- Class['site_config::default'] -> Class['tapicero']
-
- include site_config::ruby::dev
-
- #
- # USER AND GROUP
- #
-
- group { 'tapicero':
- ensure => present,
- allowdupe => false;
- }
-
- user { 'tapicero':
- ensure => present,
- allowdupe => false,
- gid => 'tapicero',
- home => '/srv/leap/tapicero',
- require => Group['tapicero'];
- }
-
- #
- # TAPICERO FILES
- #
-
- file {
-
- #
- # TAPICERO DIRECTORIES
- #
-
- '/srv/leap/tapicero':
- ensure => directory,
- owner => 'tapicero',
- group => 'tapicero',
- require => User['tapicero'];
-
- '/var/lib/leap/tapicero':
- ensure => directory,
- owner => 'tapicero',
- group => 'tapicero',
- require => User['tapicero'];
-
- # for pid file
- '/var/run/tapicero':
- ensure => directory,
- owner => 'tapicero',
- group => 'tapicero',
- require => User['tapicero'];
-
- #
- # TAPICERO CONFIG
- #
-
- '/etc/leap/tapicero.yaml':
- content => template('tapicero/tapicero.yaml.erb'),
- owner => 'tapicero',
- group => 'tapicero',
- mode => '0600',
- notify => Service['tapicero'];
-
- #
- # TAPICERO INIT
- #
-
- '/etc/init.d/tapicero':
- source => 'puppet:///modules/tapicero/tapicero.init',
- owner => root,
- group => 0,
- mode => '0755',
- require => Vcsrepo['/srv/leap/tapicero'];
- }
-
- #
- # TAPICERO CODE
- #
-
- vcsrepo { '/srv/leap/tapicero':
- ensure => present,
- force => true,
- revision => $sources['tapicero']['revision'],
- provider => $sources['tapicero']['type'],
- source => $sources['tapicero']['source'],
- owner => 'tapicero',
- group => 'tapicero',
- require => [ User['tapicero'], Group['tapicero'] ],
- notify => Exec['tapicero_bundler_update']
- }
-
- exec { 'tapicero_bundler_update':
- cwd => '/srv/leap/tapicero',
- command => '/bin/bash -c "/usr/bin/bundle check || /usr/bin/bundle install --path vendor/bundle --without test development"',
- unless => '/usr/bin/bundle check',
- user => 'tapicero',
- timeout => 600,
- require => [
- Class['bundler::install'],
- Vcsrepo['/srv/leap/tapicero'],
- Class['site_config::ruby::dev'] ],
- notify => Service['tapicero'];
- }
-
- #
- # TAPICERO DAEMON
- #
-
- service { 'tapicero':
- ensure => running,
- enable => true,
- hasstatus => false,
- hasrestart => true,
- require => [ File['/etc/init.d/tapicero'],
- File['/var/run/tapicero'],
- Couchdb::Add_user[$::site_couchdb::couchdb_tapicero_user] ];
- }
-
- leap::logfile { 'tapicero': }
-}
diff --git a/puppet/modules/tapicero/templates/tapicero.yaml.erb b/puppet/modules/tapicero/templates/tapicero.yaml.erb
deleted file mode 100644
index 8b08b49c..00000000
--- a/puppet/modules/tapicero/templates/tapicero.yaml.erb
+++ /dev/null
@@ -1,52 +0,0 @@
-<%- require 'json' -%>
-
-#
-# Default configuration options for Tapicero
-#
-
-# couch connection configuration
-connection:
- protocol: "http"
- host: "localhost"
- port: <%= @couchdb_port %>
- username: <%= @couchdb_admin_user %>
- password: <%= @couchdb_admin_password %>
- prefix : ""
- suffix : ""
- netrc: "/etc/couchdb/couchdb.netrc"
-
-# file to store the last processed user record in so we can resume after
-# a restart:
-seq_dir: "/var/lib/leap/tapicero/"
-
-# Configure log_file like this if you want to log to a file instead of syslog:
-#log_file: "/var/log/leap/tapicero.log"
-#log_level: debug
-log_level: info
-
-# tapicero specific options
-options:
- # prefix for per user databases:
- db_prefix: "user-"
- mode: <%= @couchdb_mode %>
-<%- if @couchdb_replication %>
- replication: <%= @couchdb_replication.to_json %>
-<%- end -%>
-
- # security settings to be used for the per user databases
- security:
- admins:
- names:
- # We explicitly allow the admin user to access per user databases, even
- # though admin access ignores per database security we just do this to be
- # explicit about this
- - <%= @couchdb_admin_user %>
- roles: []
- members:
- names:
- - <%= @couchdb_soledad_user %>
- - <%= @couchdb_leap_mx_user %>
- roles:
- - replication
-
-
diff --git a/tests/white-box/couchdb.rb b/tests/white-box/couchdb.rb
index 5ee12ff3..edb28eac 100644
--- a/tests/white-box/couchdb.rb
+++ b/tests/white-box/couchdb.rb
@@ -9,7 +9,6 @@ class CouchDB < LeapTest
end
def test_00_Are_daemons_running?
- assert_running '^tapicero', :single => true
if multimaster?
assert_running 'bin/beam'
assert_running 'bin/epmd'
@@ -70,7 +69,7 @@ class CouchDB < LeapTest
end
def test_04_Do_ACL_users_exist?
- acl_users = ['_design/_auth', 'leap_mx', 'nickserver', 'soledad', 'tapicero', 'webapp', 'replication']
+ acl_users = ['_design/_auth', 'leap_mx', 'nickserver', 'soledad', 'webapp', 'replication']
url = couchdb_backend_url("/_users/_all_docs", :username => 'admin')
assert_get(url) do |body|
response = JSON.parse(body)
diff --git a/tests/white-box/webapp.rb b/tests/white-box/webapp.rb
index 9956eb35..8be6bde2 100644
--- a/tests/white-box/webapp.rb
+++ b/tests/white-box/webapp.rb
@@ -95,7 +95,7 @@ class Webapp < LeapTest
end
#
- # returns true if the per-user db created by tapicero exists.
+ # returns true if the per-user db created by soledad-server exists.
# we try three times, and give up after that.
#
def assert_user_db_exists(user)