Browse Source

Improved test suite, rewritten in python

Added friendly_networks variable to denote whitelisted networks
Luke Cyca 10 years ago
parent
commit
e46ad018ba

+ 1
- 0
.gitignore View File

@@ -1,2 +1,3 @@
1 1
 .vagrant
2 2
 vagrant_ansible_inventory_default
3
+tests.pyc

+ 4
- 4
roles/common/templates/etc_fail2ban_jail.local.j2 View File

@@ -1,5 +1,5 @@
1 1
 [DEFAULT]
2
-ignoreip  = 127.0.0.1 {{ ansible_default_ipv4.address }}
2
+ignoreip  = 127.0.0.1 {{ ansible_default_ipv4.address }} {{ ' '.join(friendly_networks) }}
3 3
 bantime   = 86400
4 4
 destemail = {{ admin_email }}
5 5
 banaction = iptables-multiport
@@ -9,17 +9,17 @@ action    = %(action_mwl)s
9 9
 [ssh]
10 10
 enabled   = true
11 11
 maxretry  = 3
12
- 
12
+
13 13
 [pam-generic]
14 14
 enabled   = true
15 15
 banaction = iptables-allports
16
- 
16
+
17 17
 [ssh-ddos]
18 18
 enabled   = true
19 19
 
20 20
 [apache]
21 21
 enabled = true
22
- 
22
+
23 23
 [postfix]
24 24
 enabled  = true
25 25
 maxretry = 1

+ 2
- 2
roles/mailserver/templates/etc_postfix_main.cf.j2 View File

@@ -74,8 +74,8 @@ myorigin = $mydomain
74 74
 alias_maps = hash:/etc/aliases
75 75
 alias_database = hash:/etc/aliases
76 76
 mydestination = localhost
77
-relayhost = 
78
-mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
77
+relayhost =
78
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 {{ ' '.join(friendly_networks) }}
79 79
 #mailbox_command = procmail -a "$EXTENSION"
80 80
 mailbox_size_limit = 0
81 81
 recipient_delimiter = +

+ 260
- 0
tests.py View File

@@ -0,0 +1,260 @@
1
+import unittest
2
+from time import sleep
3
+import uuid
4
+import socket
5
+import requests
6
+import os
7
+
8
+TEST_SERVER = 'sovereign.local'
9
+TEST_ADDRESS = 'sovereign@sovereign.local'
10
+TEST_PASSWORD = 'foo'
11
+CA_BUNDLE = 'roles/common/files/wildcard_ca.pem'
12
+
13
+
14
+socket.setdefaulttimeout(5)
15
+os.environ['REQUESTS_CA_BUNDLE'] = CA_BUNDLE
16
+
17
+
18
+class SSHTests(unittest.TestCase):
19
+    def test_ssh_banner(self):
20
+        """SSH is responding with its banner"""
21
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
22
+        s.connect((TEST_SERVER, 22))
23
+        data = s.recv(1024)
24
+        s.close()
25
+
26
+        self.assertRegexpMatches(data, '^SSH-2.0-OpenSSH')
27
+
28
+
29
+class WebTests(unittest.TestCase):
30
+    def test_blog_http(self):
31
+        """Blog is redirecting to https"""
32
+        # FIXME: requests won't verify sovereign.local with *.sovereign.local cert
33
+        r = requests.get('http://' + TEST_SERVER, verify=False)
34
+
35
+        # We should be redirected to https
36
+        self.assertEquals(r.history[0].status_code, 301)
37
+        self.assertEquals(r.url, 'https://' + TEST_SERVER + '/')
38
+
39
+        # 403 - Since there is no documents in the blog directory
40
+        self.assertEquals(r.status_code, 403)
41
+
42
+    def test_webmail_http(self):
43
+        """Webmail is redirecting to https and displaying login page"""
44
+        r = requests.get('http://mail.' + TEST_SERVER)
45
+
46
+        # We should be redirected to https
47
+        self.assertEquals(r.history[0].status_code, 301)
48
+        self.assertEquals(r.url, 'https://mail.' + TEST_SERVER + '/')
49
+
50
+        # 200 - We should be at the login page
51
+        self.assertEquals(r.status_code, 200)
52
+        self.assertIn(
53
+            'Welcome to Roundcube Webmail',
54
+            r.content
55
+        )
56
+
57
+    def test_owncloud_http(self):
58
+        """ownCloud is redirecting to https and displaying login page"""
59
+        r = requests.get('http://cloud.' + TEST_SERVER)
60
+
61
+        # We should be redirected to https
62
+        self.assertEquals(r.history[0].status_code, 301)
63
+        self.assertEquals(r.url, 'https://cloud.' + TEST_SERVER + '/')
64
+
65
+        # 200 - We should be at the login page
66
+        self.assertEquals(r.status_code, 200)
67
+        self.assertIn(
68
+            'ownCloud',
69
+            r.content
70
+        )
71
+
72
+    def test_znc_http(self):
73
+        """ZNC web interface is displaying login page"""
74
+        # FIXME: requests won't verify sovereign.local with *.sovereign.local cert
75
+        r = requests.get('https://' + TEST_SERVER + ':6697', verify=False)
76
+        self.assertEquals(r.status_code, 200)
77
+        self.assertIn(
78
+            "Welcome to ZNC's web interface!",
79
+            r.content
80
+        )
81
+
82
+
83
+class IRCTests(unittest.TestCase):
84
+    def test_irc_auth(self):
85
+        """ZNC is accepting encrypted logins"""
86
+        import ssl
87
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
88
+        ssl_sock = ssl.wrap_socket(s, ca_certs=CA_BUNDLE, cert_reqs=ssl.CERT_REQUIRED)
89
+        ssl_sock.connect((TEST_SERVER, 6697))
90
+
91
+        # Check the encryption parameters
92
+        cipher, version, bits = ssl_sock.cipher()
93
+        self.assertEquals(cipher, 'AES256-SHA')
94
+        self.assertEquals(version, 'TLSv1/SSLv3')
95
+        self.assertEquals(bits, 256)
96
+
97
+        # Login
98
+        ssl_sock.send('CAP REQ sasl multi-prefix\r\n')
99
+        ssl_sock.send('PASS foo\r\n')
100
+        ssl_sock.send('NICK sovereign\r\n')
101
+        ssl_sock.send('USER sovereign 0 * Sov\r\n')
102
+
103
+        # Read until we see the ZNC banner (or timeout)
104
+        while 1:
105
+            r = ssl_sock.recv(1024)
106
+            if 'Connected to ZNC' in r:
107
+                break
108
+
109
+
110
+def new_message(from_email, to_email):
111
+    """Creates an email (headers & body) with a random subject"""
112
+    from email.mime.text import MIMEText
113
+    msg = MIMEText('Testing')
114
+    msg['Subject'] = uuid.uuid4().hex[:8]
115
+    msg['From'] = from_email
116
+    msg['To'] = to_email
117
+    return msg.as_string(), msg['subject']
118
+
119
+
120
+class MailTests(unittest.TestCase):
121
+    def assertEmailReceived(self, subject):
122
+        """Connects with IMAP and asserts the existance of an email, then deletes it"""
123
+        import imaplib
124
+
125
+        sleep(1)
126
+
127
+        # Login to IMAP
128
+        m = imaplib.IMAP4_SSL(TEST_SERVER, 993)
129
+        m.login(TEST_ADDRESS, TEST_PASSWORD)
130
+        m.select()
131
+
132
+        # Assert the message exist
133
+        typ, data = m.search(None, '(SUBJECT \"{}\")'.format(subject))
134
+        self.assertTrue(len(data[0].split()), 1)
135
+
136
+        # Delete it & logout
137
+        m.store(data[0].strip(), '+FLAGS', '\\Deleted')
138
+        m.expunge()
139
+        m.close()
140
+        m.logout()
141
+
142
+    def test_imap_requires_ssl(self):
143
+        """IMAP without SSL is NOT available"""
144
+        import imaplib
145
+
146
+        with self.assertRaisesRegexp(socket.timeout, 'timed out'):
147
+            imaplib.IMAP4(TEST_SERVER, 143)
148
+
149
+    def test_smtps(self):
150
+        """Email sent from an MUA via SMTPS is delivered"""
151
+        import smtplib
152
+        msg, subject = new_message(TEST_ADDRESS, 'root@sovereign.local')
153
+        s = smtplib.SMTP_SSL(TEST_SERVER, 465)
154
+        s.login(TEST_ADDRESS, TEST_PASSWORD)
155
+        s.sendmail(TEST_ADDRESS, ['root@sovereign.local'], msg)
156
+        s.quit()
157
+        self.assertEmailReceived(subject)
158
+
159
+    def test_smtps_requires_auth(self):
160
+        """SMTPS with no authentication is rejected"""
161
+        import smtplib
162
+        s = smtplib.SMTP_SSL(TEST_SERVER, 465)
163
+
164
+        with self.assertRaisesRegexp(smtplib.SMTPRecipientsRefused, 'Access denied'):
165
+            s.sendmail(TEST_ADDRESS, ['root@sovereign.local'], 'Test')
166
+
167
+        s.quit()
168
+
169
+    def test_smtp(self):
170
+        """Email sent from an MTA is delivered"""
171
+        import smtplib
172
+        msg, subject = new_message('someone@example.com', TEST_ADDRESS)
173
+        s = smtplib.SMTP(TEST_SERVER, 25)
174
+        s.sendmail('someone@example.com', [TEST_ADDRESS], msg)
175
+        s.quit()
176
+        self.assertEmailReceived(subject)
177
+
178
+    def test_smtp_tls(self):
179
+        """Email sent from an MTA via SMTP+TLS is delivered"""
180
+        import smtplib
181
+        msg, subject = new_message('someone@example.com', TEST_ADDRESS)
182
+        s = smtplib.SMTP(TEST_SERVER, 25)
183
+        s.starttls()
184
+        s.sendmail('someone@example.com', [TEST_ADDRESS], msg)
185
+        s.quit()
186
+        self.assertEmailReceived(subject)
187
+
188
+    def test_smtps_headers(self):
189
+        """Email sent from an MUA has DKIM and TLS headers"""
190
+        import smtplib
191
+        import imaplib
192
+
193
+        # Send a message to root
194
+        msg, subject = new_message(TEST_ADDRESS, 'root@sovereign.local')
195
+        s = smtplib.SMTP_SSL(TEST_SERVER, 465)
196
+        s.login(TEST_ADDRESS, TEST_PASSWORD)
197
+        s.sendmail(TEST_ADDRESS, ['root@sovereign.local'], msg)
198
+        s.quit()
199
+
200
+        sleep(1)
201
+
202
+        # Get the message
203
+        m = imaplib.IMAP4_SSL(TEST_SERVER, 993)
204
+        m.login(TEST_ADDRESS, TEST_PASSWORD)
205
+        m.select()
206
+        _, res = m.search(None, '(SUBJECT \"{}\")'.format(subject))
207
+        _, data = m.fetch(res[0], '(RFC822)')
208
+
209
+        self.assertIn(
210
+            'DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sovereign.local;',
211
+            data[0][1]
212
+        )
213
+
214
+        self.assertIn(
215
+            '(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))',
216
+            data[0][1]
217
+        )
218
+
219
+        # Clean up
220
+        m.store(res[0].strip(), '+FLAGS', '\\Deleted')
221
+        m.expunge()
222
+        m.close()
223
+        m.logout()
224
+
225
+    def test_smtp_headers(self):
226
+        """Email sent from an MTA via SMTP+TLS has X-DSPAM and TLS headers"""
227
+        import smtplib
228
+        import imaplib
229
+
230
+        # Send a message to root
231
+        msg, subject = new_message('someone@example.com', TEST_ADDRESS)
232
+        s = smtplib.SMTP(TEST_SERVER, 25)
233
+        s.starttls()
234
+        s.sendmail('someone@example.com', [TEST_ADDRESS], msg)
235
+        s.quit()
236
+
237
+        sleep(1)
238
+
239
+        # Get the message
240
+        m = imaplib.IMAP4_SSL(TEST_SERVER, 993)
241
+        m.login(TEST_ADDRESS, TEST_PASSWORD)
242
+        m.select()
243
+        _, res = m.search(None, '(SUBJECT \"{}\")'.format(subject))
244
+        _, data = m.fetch(res[0], '(RFC822)')
245
+
246
+        self.assertIn(
247
+            'X-DSPAM-Result: Innocent',
248
+            data[0][1]
249
+        )
250
+
251
+        self.assertIn(
252
+            '(using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))',
253
+            data[0][1]
254
+        )
255
+
256
+        # Clean up
257
+        m.store(res[0].strip(), '+FLAGS', '\\Deleted')
258
+        m.expunge()
259
+        m.close()
260
+        m.logout()

+ 0
- 55
tests.sh View File

@@ -1,55 +0,0 @@
1
-#!/bin/bash
2
-
3
-# use timeout or gtimeout
4
-export TIMEOUT="timeout"
5
-if [ -z `which timeout` ]; then
6
-    export TIMEOUT="gtimeout"
7
-fi
8
-
9
-SUITE_RET=0
10
-
11
-runtest() {
12
-  NAME=$1
13
-  OUTPUT=`$TIMEOUT -k 1 5 bash -c "$2" 2>&1`
14
-  RET="$?"
15
-  if [ $RET -eq 0 ]; then
16
-    printf "$NAME: \e[00;32mPASSED\e[00m\n"
17
-  elif [ $RET -eq 124 ]; then
18
-    printf "$NAME: \e[00;31mTIMEOUT\e[00m\n"
19
-    SUITE_RET=1
20
-  else
21
-    printf "$NAME: \e[00;31mFAILED\e[00m\n"
22
-    echo "$OUTPUT"
23
-    SUITE_RET=1
24
-  fi
25
-}
26
-
27
-
28
-
29
-# SSH
30
-runtest test_ssh "nc -w1 172.16.100.2 22 | grep '^SSH'"
31
-
32
-# SMTP
33
-runtest test_smtp "echo 'quit' | nc -w1 172.16.100.2 25 | grep 'ESMTP Postfix'"
34
-runtest test_smtps "echo '' | openssl s_client -connect 172.16.100.2:465 | grep 'TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA'"
35
-runtest test_smtp_tls "echo '' | openssl s_client -connect 172.16.100.2:25 -starttls smtp | grep 'TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA'"
36
-
37
-# IMAP
38
-runtest test_imaps "echo '' | openssl s_client -connect 172.16.100.2:993 | grep 'TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA'"
39
-
40
-# HTTP/S
41
-runtest test_http "echo 'GET /' | nc -w1 172.16.100.2 80 | grep 'It works!'"
42
-runtest test_https "echo '' | openssl s_client -connect 172.16.100.2:443 | grep 'TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA'"
43
-runtest test_blog_redirect "curl 172.16.100.2:80 -H 'Host: sovereign.local' -v | grep '301 Moved Permanently'"
44
-
45
-# The blog will give 403 because it is an empty directory
46
-runtest test_blog "curl https://172.16.100.2:443/ -H 'Host: sovereign.local' -v --insecure | grep '403 Forbidden'"
47
-
48
-# Other web sites
49
-runtest test_roundcube "curl https://172.16.100.2:443/ -H 'Host: mail.sovereign.local' -v --insecure | grep 'Welcome to Roundcube Webmail'"
50
-runtest test_owncloud "curl https://172.16.100.2:443/ -H 'Host: cloud.sovereign.local' -v --insecure | grep 'ownCloud'"
51
-
52
-# ZNC
53
-runtest test_znc "echo '' | openssl s_client -connect 172.16.100.2:6697 | grep 'TLSv1/SSLv3, Cipher is AES256-SHA'"
54
-
55
-exit $SUITE_RET

+ 2
- 0
vars/defaults.yml View File

@@ -9,6 +9,8 @@
9 9
 # main_user_name: (required)
10 10
 admin_email: "{{ main_user_name }}@{{ domain }}"
11 11
 # encfs_password: (required)
12
+friendly_networks:
13
+  - ""
12 14
 
13 15
 # ircbouncer
14 16
 znc_version: 1.0

+ 2
- 0
vars/testing.yml View File

@@ -8,6 +8,8 @@
8 8
 domain: sovereign.local
9 9
 main_user_name: sovereign
10 10
 encfs_password: testPassword
11
+friendly_networks:
12
+  - "172.16.100.0/24"
11 13
 
12 14
 # ircbouncer
13 15
 irc_nick: sovereign

Loading…
Cancel
Save