Версия для печати

В очередном продолжении рассказов про построения web-сервисов, хочу рассмотреть вопросы безопасности и аутентификации общения soap-клиента и soap-сервера.

Пусть условия таковы:

  1. сервер и клиент общаются друг с другом через публичный интернет, который наполнен снифферами;
  2. сервер имеет статический ip-адрес, а клиент находится за nat-ом;
  3. вместе с клиентом за тем же nat-ом находятся опасные соседи, которые могут и хотят получить к чему-нибудь не авторизованный доступ;
  4. и пусть клиента за этим nat-ом два: у них разное по функциональности ПО, с разными возможностями по построению soap-клиентов;
  5. ну а мы, естественно, обмениваемся супер секретными данными. :)

И так, как было рассмотрено в предыдущих постах, собираем 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. Подробнее о том, какие поля есть и как их проверять — есть в документации: Apache Module mod_ssl

Клиент же на своей машине должен сгенерировать свой приватный ключ, по ключу создать запрос на сертификат (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: SSL/TLS Strong Encryption: How-To

5. Вот и всё. Этого достаточно, что бы сделать обмен данными между SOAP-сервером и SOAP-клиентом достаточно безопасным, а доступ — авторизованным.

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


Антиспам-картинка