В очередном продолжении рассказов про построения web-сервисов, хочу рассмотреть вопросы безопасности и аутентификации общения soap-клиента и soap-сервера.
Пусть условия таковы:
- сервер и клиент общаются друг с другом через публичный интернет, который наполнен снифферами;
- сервер имеет статический ip-адрес, а клиент находится за nat-ом;
- вместе с клиентом за тем же nat-ом находятся опасные соседи, которые могут и хотят получить к чему-нибудь не авторизованный доступ;
- и пусть клиента за этим nat-ом два: у них разное по функциональности ПО, с разными возможностями по построению soap-клиентов;
- ну а мы, естественно, обмениваемся супер секретными данными. :)
И так, как было рассмотрено в предыдущих постах, собираем soap-сервер на apache2:
1. для решения проблемы №1 (публичного интернета) – пускаем весь трафик через https. Для этого конфигурируем ssl виртуальный хост:
<VirtualHost *:8443> SSLEngine on SSLRequireSSL SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0 SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL # Указываем путь к файлам с приватным ключом и сертификатом SSLCertificateFile /usr/local/apache2/conf/ssl/server/server.crt SSLCertificateKeyFile /usr/local/apache2/conf/ssl/server/server.key SSLVerifyClient none <Location /soapagent> SetHandler mod_python PythonPath "['/usr/local/apache2-soap/soapagent']+sys.path" PythonHandler soapagent_handler </Location> </VirtualHost>
В случае публичного сервиса, SSL сертификат лучше купить (подписать) у известного Certificate Authority (CA): Thawte, VeriSign, Comodo. Но так как у нас не публичный сервис, то ограничимся самоподписанным сертификатом. Для этого сделаем несколько директорий:
$ mkdir -p /usr/local/apache2/conf/ssl/server $ mkdir -p /usr/local/apache2/conf/ssl/ca $ mkdir -p /usr/local/apache2/conf/ssl/clients $ chown -R root:wheel /usr/local/apache2/conf/ssl $ chmod -R 700 /usr/local/apache2/conf/ssl
создадим ключ и подпишем сертификат для сервера:
$ cd /usr/local/apache2/conf/ssl/server $ openssl genrsa -out server.key 1024 $ openssl req -new -key server.key -out server.csr $ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
Указав в настройках web-сервера пути к ключу и сертификату (SSLCertificateFile, SSLCertificateKeyFile) мы получим SSL хост. Теперь достаточно в WSDL файле указать путь к методам сервиса через https и мы получим web-сервисы через SSL:
<soap:address location="https://localhost:8443/soapagent">
2. Дабы не искушать хакеров, ограничим доступ к нашему сервису с ip-адреса nat-а (111.222.111.222). Это не спасёт нас от хакеров из-за этого же nat-а, но поможет ото всех остальных. Немного модифицируем наш конфиг хоста:
<Location /soapagent> # Это ограничение по ip Order allow,deny Allow from 111.222.111.222 # SetHandler mod_python PythonPath "['/usr/local/apache2-soap/soapagent']+sys.path" PythonHandler soapagent_handler </Location>
3-a. Что бы как-то ограничить доступ из-за nat-а, добавим авторизацию для клиентов. В простом случае достаточно включить Basic-auth. Так как наш трафик ходит через https, то этот метод можно считать достаточно надёжным. Немного модифицируем наш конфиг хоста:
<Location /soapagent> # Это ограничение по ip Order allow,deny Allow from 111.222.111.222 # # Это basic-auth по паролю Authtype Basic Authname "Private Area" AuthUserFile /usr/local/apache2/conf/ssl/users.pwd Require valid-user # SetHandler mod_python PythonPath "['/usr/local/apache2-soap/soapagent']+sys.path" PythonHandler soapagent_handler </Location>
И создадим файл с логином и паролем:
$ /usr/local/apache2/bin/htpasswd -cm /usr/local/apache2/conf/ssl/users.pwd vasia_pupkin
Тогда наш SOAP-клиент будет выглядеть так:
#!/usr/bin/env python
import sys
from soapagent_client import *
def main():
loc = soapagentLocator()
username = 'vasia_pupkin'
password = 'password'
port = loc.getsoapagentPort(tracefile=sys.stdout, auth=(ZSI.client.AUTH.httpbasic, username, password))
msg = SUM()
msg._aVal = 1
msg._bVal = 2
response = port.SUM(msg)
print response.__dict__
if __name__ == "__main__": main()
3-b. Для большей надёжности вместо(или вместе) Basic-аутентификации по паролю можно использовать аутентификацию по SSL-сертификатам. Для этого генерируем ключ CA и сами же подпишем свой CA-сертификат:
$ cd /usr/local/apache2/conf/ssl/ca $ openssl genrsa -out ca.key 1024 $ openssl req -new -key ca.key -out ca.csr $ openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
Так как это наш собственный CA, то поля при генирации запроса на сертификат заполняем по своему вкусу.
Далее, подписывая этим CA сертификаты клиентов и требуя именно таких сертификатов при доступе к ресурсу, мы сможем ограничить доступ. Что бы включить эту проверку на сервере, делаем вот такую конфигурацию вирт.хоста:
<VirtualHost *:8443>
SSLEngine on
SSLRequireSSL
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
# Путь к файлу с сертификатом нашего CA
SSLCACertificateFile /usr/local/apache2/conf/ssl/ca/ca.crt
SSLCertificateFile /usr/local/apache2/conf/ssl/server/server.crt
SSLCertificateKeyFile /usr/local/apache2/conf/ssl/server/server.key
SSLVerifyClient none
<Location /soapagent>
# Это ограничение по ip
Order allow,deny
Allow from 256.256.256.256
#
# Это basic-auth по паролю
Authtype Basic
Authname "Private Area"
AuthUserFile /usr/local/apache2/conf/ssl/users.pwd
Require valid-user
#
# Это проверка клиентских сертификатов
SSLVerifyClient require
SSLVerifyDepth 1
# SSLRequire %{SSL_CLIENT_S_DN_CN} eq "vasia-pupkin"
#
SetHandler mod_python
PythonPath "['/usr/local/apache2-soap/soapagent']+sys.path"
PythonHandler soapagent_handler
</Location>
</VirtualHost>
Мы можем проверять как просто факт наличия у клиента сертификата(SSLVerifyClient require), так и присутствие в его сертификате определённых значений. Например: SSLRequire %{SSL_CLIENT_S_DN_CN} eq «vasia-pupkin» проверяет, что у клиента в сертификате поле CommonName содержит vasia-pupkin. Подробнее о том, какие поля есть и как их проверять — есть в документации:
Клиент же на своей машине должен сгенерировать свой приватный ключ, по ключу создать запрос на сертификат (csr) и прислать этот запрос нам на подпись. На стороне клиента делаем:
$ openssl genrsa -out client.key 1024 $ openssl req -new -key client.key -out client.csr
Если мы хотим проверять какие-то определённые поля сертификата, то клиент при создании запроса должен правильно заполнить эти поля.
Далее переносим csr на сервер, подписываем этот сертификат нашим CA:
$ cd /usr/local/apache2/conf/ssl/clients $ openssl x509 -req -days 365 -CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial -in client.csr -out client.crt
и отдаём получившийся сертификат (client.crt) клиенту. Теперь SOAP-клиент будет выглядеть так:
#!/usr/bin/env python
import sys
from soapagent_client import *
def main():
loc = soapagentLocator()
username = 'vasia_pupkin'
password = 'password'
SSLtransdict = {'cert_file': 'client.crt', 'key_file': 'client.key'}
port = loc.getsoapagentPort(tracefile=sys.stdout, transdict=SSLtransdict ,auth=(ZSI.client.AUTH.httpbasic, username, password))
msg = SUM()
msg._aVal = 1
msg._bVal = 2
response = port.SUM(msg)
print response.__dict__
if __name__ == "__main__": main()
Все, получаемые описанными выше способами, сертификаты будут в формате PEM (Privacy Enhanced Mail), что по сути представляет собой base64-закодированный DER (Distinguished Encoding Rules) сертификат. Некоторые клиенты умеют работать только с определённым видом сертификата. При помощи openssl их можно легко конвертировать один в другой:
# PKCS#12 (Netscape, IE etc) из PEM $ openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12 # DER из PEM $ openssl -inform PEM -outform DER -in client.crt -out client-der.crt
4. Таким образом мы ограничили доступ по ip и включили два вида аутентификации для клиентов. SOAP-клиент на ZSI поддерживает все эти способы, но есть клиенты, которые не умеют, например, аутентификацию по SSL-сертификатам. Для них можно сделать отдельные <Location> и использовать для каждого из <Location> свой метод аутентификации. Более подробно про это можно прочитать в документации на Apache:
5. Вот и всё. Этого достаточно, что бы сделать обмен данными между SOAP-сервером и SOAP-клиентом достаточно безопасным, а доступ — авторизованным.









Оставить комментарий