Szyfrujemy transmisję HTTP / Apache 1.3.37 + SSL

Szyfrowanie jest procedurą przekształcania tekstu z postaci jawnej, czytelnej dla człowieka na postać, której nie da się wprost odczytać. W momencie przechwycenia transmisji danych bez użycia żadnych mechanizmów szyfrujących intruz jest w stanie odczytać wszystkie przesyłane dane. Wyobraźmy sobie logowanie do poczty poprzez interfejs WWW. Użytkownik aby móc się uwierzytelnić musi podać swój login oraz hasło. W tej samej sieci jest intruz, którego zadaniem jest przechwycenie tajnych danych użytkownika. Aby to było możliwe wystarczy, aby sieć komputerowa, do której są obaj podłączeni była siecią Ethernet zbudowaną w oparciu o zwykłe huby Ethernetowi, które nie posiadają możliwości izolacji portów.

Do przechwytywania transmisji danych w sieciach ethernet służą narzędzia, nazywane snifferami. Najpopularniejsze z nich to tcpdump, ethereal, wireshark. Chciałbym zaprezentować poniżej jak może wyglądać przechwycona niezaszyfrowana transmisja danych pomiędzy przeglądarką a serwerem HTTP.

Na poniższym rysunku widać, że przechwycenie transmisji jawnej, automatycznie ujawnia nasze tajne dane przesyłane z użyciem metody GET, takie jak login i hasło w tym przypadku, user=jamzed i pass=trudnehaslo.

Gdybyśmy używali protokołu HTTPS, czyli wykorzystywali transmisję szyfrowaną, to przechwycenie ruchu nie ujawni żadnych danych, poniżej przykładowy zrzut transmisji:

Gdy przesyłamy w Internecie jakiekolwiek dane zawierające informacje, które nie są publiczne, zawsze powinniśmy wykorzystywać transmisję szyfrowaną. Pozwoli to nam uniknąć sytuacji, w której nasze dane zostają odczytane przez pośrednie osoby.

Mechanizm SSL zapewnia szyfrowanie danych przesyłanych między serwerem a klientem, dodatkowo daje możliwość weryfikacji tożsamości serwera. Polega to na tym, że każdy certyfikat powinien być podpisane przez zaufane centrum autoryzacyjne, które poświadcza, że dany certyfikat jest prawidłowy dla danej konkretnej domeny, dla której został wygenerowany. Dodatkowym zabezpieczeniem dostępu do zasobów danych może być umieszczenie ich na serwerze i wymuszenie na kliencie weryfikacji tożsamości przy próbie dostępu. W takim przypadku klient musi przedstawić swój prywatny certyfikat, który jest dodany jako zaufany na serwerze HTTP.

Aby zaimplementować obsługę protokołu SSL dla serwera Apache należy zainstalować pakiet OpenSSL, który zawiera niezbędne biblioteki do kompilacji modułu mod_ssl. Poza pakietem OpenSSL system operacyjny musi mieć możliwość generowania wartości pseudolosowych, czyli w przypadku systemów Unix/Linux jest to domyślne pseudo urządzenie /dev/random. Dla Apache serii 1.3.X implementacja mod_ssl wygląda następująco:

  • pobranie źródeł modułu mod_ssl
  • rozpakowanie
  • skonfigurowanie i kompilacja serwera Apache

deface Sources # wget http://www.modssl.org/source/mod_ssl-2.8.28-1.3.37.tar.gz
deface Sources # tar zxfv mod_ssl-2.8.28-1.3.37.tar.gz -C Unpacked/
deface Sources # cd Unpacked/mod_ssl-2.8.28-1.3.37/
deface mod_ssl-2.8.28-1.3.37 # ./configure --with-apache=../apache_1.3.37/
Configuring mod_ssl/2.8.28 for Apache/1.3.37
+ Apache location: ../apache_1.3.37/ (Version 1.3.37)
+ Auxiliary patch tool: ./etc/patch/patch (local)
+ Applying packages to Apache source tree:
o Extended API (EAPI)
o Distribution Documents
o SSL Module Source
o SSL Support
o SSL Configuration Additions
o SSL Module Documentation
o Addons
Done: source extension and patches successfully applied.

Now proceed with the following commands (Bourne-Shell syntax):
$ cd ../apache_1.3.37/
$ SSL_BASE=/path/to/openssl ./configure ... --enable-module=ssl
$ make
$ make certificate
$ make install

W tym momencie zgodnie z informacją przechodzimy do katalogu ze źródłami serwera HTTP Apache 1.3.37 i wydajemy wszystkie polecenia dokładnie w takiej kolejności:

deface apache_1.3.37 # ./configure --prefix=/usr/local/apache/ --enable-module=so --enable-module=rewrite --enable-module=ssl

Najważniejszym parametrem jest --enable-module=ssl, który powoduje, że pozostała konfiguracja uwzględnia moduł SSL:

Configuring for Apache, Version 1.3.37
+ using installation path layout: Apache (config.layout)
Creating Makefile
Creating Configuration.apaci in src
Creating Makefile in src
+ configured for Linux platform
+ setting C compiler to gcc
+ setting C pre-processor to gcc -E
+ using "tr [a-z] [A-Z]" to uppercase
+ checking for system header files
+ adding selected modules
o rewrite_module uses ConfigStart/End
disabling DBM support for mod_rewrite
(perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)
o ssl_module uses ConfigStart/End
+ SSL interface: mod_ssl/2.8.28
+ SSL interface build type: OBJ
+ SSL interface compatibility: enabled
+ SSL interface experimental code: disabled
+ SSL interface conservative code: disabled
+ SSL interface vendor extensions: disabled
+ SSL interface plugin: Built-in SDBM
+ SSL library path: [SYSTEM]
+ SSL library version: OpenSSL 0.9.8d 28 Sep 2006
+ SSL library type: installed package (system-wide)
+ enabling Extended API (EAPI)
+ using system Expat
+ using -ldl for vendor DSO support
+ checking sizeof various data types
+ doing sanity check on compiler and options
Creating Makefile in src/support
Creating Makefile in src/regex
Creating Makefile in src/os/unix
Creating Makefile in src/ap
Creating Makefile in src/main
Creating Makefile in src/modules/standard
Creating Makefile in src/modules/ssl

Po zakończonym poleceniu ./configure należy wydać polecenie make, które spowoduje, że odbędzie się pełna kompilacja serwera Apache, zakończona komunikatem:

Before you install the package you now should prepare the SSL
certificate system by running the 'make certificate' command.
For different situations the following variants are provided:

% make certificate TYPE=dummy    (dummy self-signed Snake Oil cert)
% make certificate TYPE=test     (test cert signed by Snake Oil CA)
% make certificate TYPE=custom   (custom cert signed by own CA)
% make certificate TYPE=existing (existing cert)
CRT=/path/to/your.crt [KEY=/path/to/your.key]

Use TYPE=dummy    when you're a  vendor package maintainer,
the TYPE=test     when you're an admin but want to do tests only,
the TYPE=custom   when you're an admin willing to run a real server
and TYPE=existing when you're an admin who upgrades a server.
(The default is TYPE=test)

Additionally add ALGO=RSA (default) or ALGO=DSA to select
the signature algorithm used for the generated certificate.

Use 'make certificate VIEW=1' to display the generated data.

Thanks for using Apache & mod_ssl.
Ralf S. Engelschall [email protected] www.engelschall.com

Pozostaje nam wygenerowanie certyfikatów. Najszybciej możemy je wygenerować poleceniem: make certificate. Zostaniemy poproszeni o podanie następujących informacji:

  • Country Name
  • State or Province Name
  • Locality Name
  • Organization Name
  • Organizational Unit Name
  • Common Name
  • Email Address
  • Certificate Validity

Po uzupełnieniu wszystkich informacji możemy wydać polecenie make install. W tym momencie nastąpi instalacja serwera HTTP z wsparciem dla SSL. Pozostaje już tylko uruchomienie serwera:

deface ssl.crt # /usr/local/apache/bin/apachectl startssl
/usr/local/apache/bin/apachectl startssl: httpd started

Po wystartowaniu możemy przetestować, czy serwer poprawnie obsługuje szyfrowanie SSL, do tego celu możemy po prostu spróbować połączyć się przeglądarką, wykorzystując protokół HTTPS.

Przy próbie załadowania strony zostaniemy poinformowani, że nasz certyfikat nie został poprawnie zweryfikowany. Wynika to z tego, że nie został podpisany przez zaufane CA, a przez nasze własne. Pomijając powyższe ostrzeżenie, sam mechanizm szyfrujący będzie działał prawidłowo.

Konfiguracja modułu mod_ssl wymaga zapoznania się w wieloma dyrektywami. Najważniejsza z nich to SSLEngine, która włącza lub wyłącza mechanizm SSL/TLS, zazwyczaj zostaje wymieniona w kontenerze VirtualHost dla portu 443, może przyjować dwie wartości ON lub OFF. Dla zwiększenia bezpieczeństwa poprzez wzmocnienie samego mechanizmu szyfrującego, możemy wykorzystać dyrektywę SSLCipherSuite, która pozwala na wyznaczenie długości klucza szyfrowania oraz algorytmu kryptograficznego. Najwyższy poziom zabezpieczeń uzyskamy jeśli dyrektywa będzie miała następującą treść:

SSLCipherSuite HIGH:MEDIUM:!aNULL:+SHA1:+MD5:+HIGH:+MEDIUM

co oznacza, że dozwolone są tylko silne algorytmy szyfrujące, ponadto zostają wyłączone słabsze algorytmy i nieszyfrujące danych. Bardzo ważny element w szyfrowaniu danych odgrywa mechanizm generowania liczb losowych. Użycie losowych wartości jako wartości wejściowej dla funkcji szyfrowania OpenSSL zdecydowanie utrudnia atakującemu predykację przyszłych danych, aby mieć wpływ na powyższe czynniki, może wykorzystać dyrektywę SSLRandomSeed. Serwer Apache pozwala na wykorzystywanie tej dyrektywy w dwóch kontekstach - startu, oraz połączenia. Zalecane ustawienia dyrektywy to:

SSLRandomSeed startup file:/dev/urandom 512
SSLRandomSeed connect file:/dev/urandom 512

Aby wskazać pliki naszego certyfikaty oraz klucza szyfrowania, musimy użyć dyrektyw SSLCertificateFile oraz SSLCertificateKeyFile, składnia wygląda następująco:

SSLCertificateFile /usr/local/apache/conf/ssl.crt/server.crt
SSLCertificateKeyFile /usr/local/apache.conf/ssl.key/server.key

Implementacja modułu mod_ssl nie jest skomplikowana, więc powinniśmy wykorzystywać ją jak najczęściej, jeśli zależy nam na zachowaniu bezpieczeństwa przesyłanych danych.

A Wy jak szyfrujecie transmisję?

strace - czyli o śledzeniu słów kilka

strace jest doskonałym narzędziem służącym do śledzenia procesów, zarówno tych już uruchomionych (możliwość podpięcia się pod działający już proces, np. daemon,) jak i uruchamianych na bieżąco. Wspominałem już o nim w artykule 10 narzędzi które powinien znać administrator, a teraz chciałbym rozszerzyć ten artykuł konkretnie o przykłady i analizy.

strace nie należy do najprostszych narzędzi, może nie tyle chodzi o samą obsługę, ale o zrozumienie wyników. Na pewno należy znać podstawy budowy systemu Linuks, ale zdecydowanie większym atutem będzie znajomość podstaw programowania w ANSI C. Kernel Linuksa jak i większość narzędzi dostępnych w systemie jest właśnie napisana w tym języku, oznacza to, że funkcje wykonywane przez programy (również programy napisane w językach wyższego poziomu), muszą być tłumaczone na funkcje języka C, a strace'owanie, polega właśnie na podglądaniu wywoływanych funkcji systemowych przez konkretny program.

Przykład 1

#include <stdio.h>
#include <stdlib.h>

int main(void) {
printf("Hello worldn");
exit(0);
}

Program po skompilowaniu (make example1) i uruchomieniu wyświetla jedynie napis Hello world, spójrzmy co zwróci nam strace ./example1

jamzed@yoursite:~/Lab$ strace ./example1
execve("./example1", ["./example1"], [/* 22 vars */]) = 0
brk(0)                                  = 0x901e000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78eb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18028, ...}) = 0
mmap2(NULL, 18028, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb78e6000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "177ELF111���������3�3�1���260l1�004���"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1319364, ...}) = 0
mmap2(NULL, 1329512, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x77e000
mprotect(0x8bc000, 4096, PROT_NONE)     = 0
mmap2(0x8bd000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x13e) = 0x8bd000
mmap2(0x8c0000, 10600, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x8c0000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78e5000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb78e56c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x8bd000, 8192, PROT_READ)     = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0xfd8000, 4096, PROT_READ)     = 0
munmap(0xb78e6000, 18028)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb78ea000
write(1, "Hello worldn", 12Hello world
)           = 12

exit_group(0)                           = ?

Na samym początku widać funkcję execve, która powoduje uruchomienie binarki example1, kolejne linie to ładowanie bibliotek niezbędnych do uruchamiania programów, pogrubione linie to coś dla nas interesującego, pierwsza i druga to otwarcie i czytanie pliku (/lib/tls/i686/cmov/libc.so.6), read(3...), pierwsza funkcja open() zwróciła numer deskryptora pliku (3) a druga read() przeczytała z tego deskryptora 512 bajtów, kolejna linia na którą warto zwrócić uwagę do fstat64(1...), funkcja fstat64 pobiera status pliku, w tym przypadku deksryptora 1 (STDOUT), następnie wywoływana jest funkcja write(1...), która powoduje zapisanie danych do konkretnego deskryptora (1). Czyli tłumacząc teraz na język ludzki, nasza aplikacja wykonała wypisanie "Hello world" do 1 deskryptora (STDOUT), czyli na nasz ekran.

Przytoczyłem powyższy przykład, żeby pokazać jak wygląda standardowy output ze strace'a, tak naprawdę to każda czynność wykonywana przez jakąś aplikację to wywoływanie konkretnych funkcji, otwarcie pliku open(), zamknięcie pliku/deskryptora close(), pisanie write(), czytanie read()... itd... jeśli nie wiemy jak działa któraś z funkcji, możemy skorzystać z doskonałej pomocy jaką są manuale, np. man 2 write.

ssize_t write(int fd, const void *buf, size_t count);

man mówi nam, że funkcja write zwraca wynik jako ssize_t (czyli ilość przeczytanych bajtów), a jako argumenty przyjmuje (int deskryptor, wskaźnik do bufora z danymi, rozmiar bufora z danymi), na naszym przykładzie, deskryptor to STDOUT, wskaźnik do bufora jest zastąpiony konkretnymi danymi "Hello worldn", rozmiar to 12 bajtów, wszystko się zgadza.

W kolejnym przykładzie pokażę, jak wygląda klasyczne połączenie TCP do zdalnego hosta, przygotowałem krótki program wykonujący połączenie TCP na adres localhost (127.0.0.1), na port 5000 i wysyłający informację.

Przykład 2

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(void) {

int sock;
char *send_data = "DANE DANE DANEn";
struct hostent *host;
struct sockaddr_in server_addr;

host = gethostbyname("localhost");
sock = socket(AF_INET, SOCK_STREAM, 0);

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(5000);
server_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(server_addr.sin_zero),8);

connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));

printf("SEND DATAn");
send(sock,send_data,strlen(send_data), 0);
close(sock);

return 0;
}

Dla lepszego zobrazowania, uruchamiamy w drugim terminalu netcata (nc -l -p 5000), co pozwoli nam obserwować czy, nasz klient TCP wykonał rzeczywiście połączenie i przesłał dane.

jamzed@yoursite:~/Lab$ strace ./example2
open("/etc/resolv.conf", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=35, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7834000
read(3, "search plnnameserver 194.204.159"..., 4096) = 35
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb7834000, 4096)                = 0
stat64("/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=35, ...}) = 0
open("/etc/resolv.conf", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=35, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7834000
read(3, "search plnnameserver 194.204.159"..., 4096) = 35
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb7834000, 4096)                = 0
socket(PF_FILE, 0x80801 /* SOCK_??? */, 0) = 3
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)                                = 0
socket(PF_FILE, 0x80801 /* SOCK_??? */, 0) = 3
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(3)                      = 0
open("/etc/nsswitch.conf", O_RDONLY)    = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=475, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7834000
read(3, "# /etc/nsswitch.confn#n# Example"..., 4096) = 475
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb7834000, 4096)                = 0
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=18028, ...}) = 0
mmap2(NULL, 18028, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7830000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libnss_files.so.2", O_RDONLY) = 3
read(3, "177ELF111���������3�3�1��� 32��004���"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0644, st_size=42572, ...}) = 0
mmap2(NULL, 45772, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x8c3000
mmap2(0x8cd000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x9) = 0x8cd000
close(3)                                = 0
mprotect(0x8cd000, 4096, PROT_READ)     = 0
munmap(0xb7830000, 18028)               = 0
open("/etc/host.conf", O_RDONLY)        = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=92, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7834000
read(3, "# The "order" line is only used "..., 4096) = 92
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb7834000, 4096)                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
fcntl64(3, F_GETFD)                     = 0x1 (flags FD_CLOEXEC)
fstat64(3, {st_mode=S_IFREG|0644, st_size=272, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7834000
read(3, "127.0.0.1tlocalhostn195.205.203."..., 4096) = 272
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0xb7834000, 4096)                = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(5000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0

fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7834000
write(1, "SEND DATAn", 10SEND DATA
)             = 10
send(3, "DANE DANE DANEn", 15, 0)      = 15
close(3)                                = 0

exit_group(0)                           = ?

Pominąłem pierwsze linie inicjalizujące nasz program, wkleiłem od momentu kiedy zaczyna się coś dziać. Podczas wykonywania połączenia TCP musi nastąpić kilka czynności, przede wszystkim  musi nastąpić zamiana hosta na adres IP, musi zostać utworzony socket, po tym dopiero connect() do konkretnego hosta i portu, i następnie można już poprawnie wykonać send(), czyli wysłać dane. Pogrubione linie właśnie pokazują jak system czyta plik /etc/resolv.conf, następnie próbuje zresolwować host localhost poprzez połączenie po sockecie do nscd (name service cache daemon), keszującego daemona, ten nie odpowiada więc następuje czytanie pliku /etc/host.conf, który określa kolejność w jakiej należy resolwować hosty:

jamzed@yoursite:~/Lab$ cat /etc/host.conf
# The "order" line is only used by old versions of the C library.
order hosts,bind

najpierw /etc/hosts a dopiero później korzystając z serwera DNS, co właśnie robi nasza aplikacja, szuka localhost w /etc/hosts i znajduje (127.0.0.1). Po tym jak znane jest IP następuje utworzenie socketu socket(PF_INET, SOCK_STREAM, IPPROTO_IP), SOCK_STREAM mówi o tym, że będzie to połączenie TCP/IP, mając utworzony socket następuje wywołanie funkcji connect(3, {sa...), jako pierwszy argument funkcja przyjmuje deskryptor naszego socketa (3), następuje więc połączenie z naszym serwerem 127.0.0.1:5000, kolejny krok to wypisanie na STDOUT stringu "SEND DATAn" i dopiero po tym jest send(3, "DANE...), czyli właściwie wysłanie danych do serwera, później pozostaje posprzątać po sobie close(3), i zakończyć poprawnie program exit(0).

Jeśli uruchomiony nasz program w momencie kiedy serwer nie będzie działał poprawnie zobaczymy w wyniku strace co dokładnie jest przyczyną problemów:

socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(5000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7871000
write(1, "SEND DATAn", 10SEND DATA
)             = 10
send(3, "DANE DANE DANEn", 15, 0)      = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
+++ killed by SIGPIPE +++

Widzimy, że funkcja connect() zwróciła -1 czyli Connection refused co świadczy o tym, że połączenie z serwerem nie udało się, nasza aplikacja nie obsługuje błędów więc próbuje pisać do deskryptora którego nie ma, stąd drugi komunikat Broken pipe. Analogiczna sytuacja może wystąpić w każdej innej aplikacji, w przeglądarce, w kliencie GG, jeśli wykonany na nim strace, a serwer nie będzie działał poprawnie, również zobaczymy podobny wynik.

Na początku cały ten output może wydawać się bardzo nieczytelny i trudny w analizie, ale z czasem przeglądając wyniki strace'a różnych aplikacji dojdziemy do wprawy i diagnozowanie nawet złożonych aplikacji/problemów nie będzie sprawiała nam dużych trudności. Jest to pierwszy artykuł poświęcony strace'owi, mam nadzieję, że nie ostatni i z czasem na przykładzie rozwiązywania konkretnych problemów strace będzie pojawiał się coraz częściej, zapraszam więc do zapoznania się z tym narzędziem (man strace).

Kod przykładów do pobrania:

A co Wy ostatnio strace'owaliście? ;-)

RDM's ESXi, fizyczny dysk dla wirtualnej maszyny

Załóżmy, że posiadamy gotowy system operacyjny (lub dane które chcemy przenieść) na istniejącym dysku fizycznym. ESXi z poziomy klienta vSphare w żadnym wypadku nie pozwala na taką akcje. Jednak jest na to milusi sposób :)
Potrzebny będzie dostęp do maszyny hosta z poziomu SSH ( jak uzyskać dostęp ssh pisałem w poprzednich artach) Czytaj dalej

Z limitem lub "bez" limitu

Limitowanie - czyli ograniczenie dostępu do szeroko pojętych usług.

Z pojęciem limitowania spotkał się każdy z Was zapewne w różnych miejscach: poczta, bazy danych, www, gsm, vps (...) - pod tym pojęciem może się kryć wiele znaczeń - nie koniecznie to czego użytkownik może się spodziewać. Używanie słow związanych z limitem może być przynętą dla osób zaintersowanych usługą, ale należy spodziwać się haczyków które czyhają aby nas zaskoczyć w najmniej oczekiwanym momencie. Przykladów użycia może być wiele np. limit transferu lub bazy danych bez limitu lub dostęp do konta bez limitu lub ilość sms'ów bez limitu czy bez limitu transferu  i wiele wiele innych  - wygląda ładnie, ale znaczeń może mieć wiele.

Jaka jest istota limitowania ?
Odpowiedź jest jasna dla wielu z Was, ale postaram się o tym powiedzieć głośno - choćby dla tych którzy się tylko tego domyślają a nie są tego pewni.
Tworzenie limitów ma na celu uchronienie infrastruktury lub pojedynczej usługi  przed nadmiernym jej eksploatowaniem - pomimo tego, że wcześniej usługodawca zarzekał się że tych limitów nie ma lub są ale nie tam gdzie użytkownik się spodziewa. Dla tych co jest to zdanie mało zrozumiane to powiem prościej: jak nie wiadomo o co chodzi to chodzi o pieniądze jakie wiążą się z utrzymaniem. Ma to dla mnie sens bo wiem jak to wygląda od "kuchni", ale jeśli kupuję usługę "bez limitów" to oczekuję właśnie _bez limitów_ a nie z limitami które są ukryte. Prawda jest taka że wcześniej czy później obijemy się o te limity - tylko jak je rozpoznać i świadomie się o nie rozbić to jest sztuka.
Inni jeszcze limitują po to aby móc zarobić na tym - ponieważ ich system IT kosztuje na tyle mało lub na tyle nie istotnie, że są wstanie pozwolić sobie na takie działania - albo poprostu taki ich model biznesowy. Przykładem być może rapid*** ktory limituje po to aby na tym zarabiać - limitują ilością konkurentnych połączeń oraz szybkością transferu ale za to plików możesz wgrywać "bez limitu" :)
Drugim dnem jest nadsubskrypcja ( ang. oversubscription ) - bez rozsądnego limitowania nie dałoby rady w pełni wykorzystać platformy.

Co warto limitować ?
Co tylko sobie wymyślimy, ale warunek musi być jeden: mamy na tym zarobić. Jednostką wynagrodzenia może być: kosztowne łącze, ram, procesor, baza danych, MTA, pojemność na dyskach lub inne korzyści które końcowo i tak wiążą się z kasą.
Limitowanie dla zabawy można robić labolatoryjnie w domu tak aby innym nie szkodzić. Przykładem zaszkodzenia mogą być firmy zarządzające RBL'ami, które listują hosty bez limitu czasowego - pomimo że taki limit mają podany i po którym powinni czyścić wpisy - to jest właśnie działanie labolatoryjne z zacięciem produkcyjnym.

Limitować możemy:

  • ilość tworzonych procesów apache / ilość obsługiwanych źądań do apacha - oszczędzamy w zasadzie pamięć
  • ilość utworzonych plików - oszczędzamy inody, w zasadzie bardzo ważne przy hostowaniu dużej ilości plików dla wielu użytkowników
  • ilość utworzonych połączeń do bazy danych - oszczędzamy ram, fakt w niektórych przypadkach nie można tego zrobić w sposób bezpośredni ale sens jest zachowany
  • ilość wysyłanych maili - oszczędzamy samych siebie przed spamcopami, rblami
  • szybkość przyjmowania maili - oszczędzamy samych siebie, po co kolejki tworzyć u siebie, jak można u innych  ?
  • ilość konkurentnych połączeń do usługi - obojętnie jaka by nie była, oszczędzamy usługę przed DoS'ami lub DDoS'ami
  • transfer - oszczędzamy na przepustowości i płynności działania usługi
  • zakładania kont per IP, domena, kraj, miasto - oszczędzamy samych siebie przed spamem/warezem
  • ilość pamięci/procesorów dla wirtualnych kontenerów

Czego nie warto limitować:

  • ilości dostępu do pliku z określonego adresu IP - jeżeli będę chciał sciągać plik 10kb co minutę to dlaczego miałby mi ktoś tego zabronić ?
  • ilości baz danych - bo niby po co ? W jednej bazie danych i tak mogę stworzyć "dowolną" liczbę tabel.
  • ilości kont - w zasadzie nie ma co tłumaczyć
  • rozmiaru plików - czy wgram sobie jeden plik 1GB czy zrobie to w porcjach po 10MB to już moja sprawa

Scenariusze jakie możemy wymyśleć:

  • Nieograniczoną ilość baz danych - ale wprowadzić limit szybkości transferu
  • FTP bez limitów szybkości transferu -  ale limitować ilość sciągnietych danych w ciągu doby
  • 2GB pojemności bez limitów transferu - ale limitować ilość połączeń konkurentnych do plików powyżej X MB
  • 2000 smsów w pakiecie - ale limitować ilość wysłanych na godzine np. 10 ( nie mniej niż 2 )
  • 5000 tyś minut w pakiecie - ale limitować do konkretnych sieci/osób

Scenariuszy można wymyśleć dużo, na jeszcze więcej można się napotkać. Najciekawsze są te kuriozalne związane z sieciami GSM ale to już inna historia.
Realizacja tego co przedstawiłem może odbywać się na różnych warstwach: od aplikacyjnej po sieciową - zależy tylko od Ciebie.

Ja już mam wypracowane modele :-)

vsftpd i MySQL - tworzymy idealną parę

Każdy administrator ma swoje zdanie na temat przechowywania użytkowników w bazie danych. Jedni będą twierdzić, że to bardzo głupi pomysł, ponieważ wprowadzamy dodatkową warstwę pomiędzy klientem a serwerem, natomiast drudzy zobaczą w tym wygodę i automatyzację. Wyobraźmy sobie taką sytuację, nasi klienci czy znajomi posiadają na naszym serwerze konta WWW, my im podpieliśmy domenę, założyliśmy konto shell, uruchomiliśmy serwer FTPd i podaliśmy im namiary. Każdy korzysta z konta na swój sposób, ale przychodzi moment, kiedy ktoś musi zmienić hasło... dzwoni więc do nas i prosi o zmianę hasła, my logujemy się, wpisujemy passwd $user i wszystko jest fajnie. Kolejnego dnia dzwoni ktoś inny i mówi, że wygasła mu domena, a nie chciał jej przedłużać bo udało mu się zarejestrować inną i prosi nas o zmianę wpisów w serwerze DNS, znów logujemy się na serwer i edytujemy pliki named'a... Wytrzymamy to, jeśli takich telefonów nie będzie 10 dziennie. Administratorzy to leniwy naród ;-) i automatyzują/upraszczają wszystko co się da. Tak naprawdę idealnym rozwiązaniem tej sytuacji jest posiadania centralnej bazy danych użytkowników/klientów/znajomych, w której będą umieszczone zarówno domeny, hasła i wszystko inne co może nam się przydać w przyszłości. Spróbujmy więc na początek uruchomić serwer FTPd działający w oparciu o autoryzację użytkowników bezpośrednio z bazy danych MySQL.

Osobiście preferuję do prostszych zastosowań serwer vsftpd, posiada wszystko co jest mi niezbędne, wsparcie SSL/TLS, chroot, umask, dodatkowo wykorzystam bazę danych MySQL, chyba najbardziej popularną wśród darmowych rozwiązań.

Instalacja VSFTPd (Ubuntu)

root@iDev:/home/jamzed# apt-get install vsftpd libpam-mysql mysql-server mysql-client
Czytanie list pakietów... Gotowe
Budowanie drzewa zależności
Odczyt informacji o stanie... Gotowe
Zostaną zainstalowane następujące dodatkowe pakiety:
libdbd-mysql-perl libdbi-perl libhtml-template-perl libmysqlclient15off libmysqlclient16 libnet-daemon-perl libplrpc-perl mysql-client-5.1 mysql-common
mysql-server-5.1 mysql-server-core-5.1 ssl-cert update-inetd
Sugerowane pakiety:
dbishell libipc-sharedcache-perl tinyca mailx
Zostaną zainstalowane następujące NOWE pakiety:
libdbd-mysql-perl libdbi-perl libhtml-template-perl libmysqlclient15off libmysqlclient16 libnet-daemon-perl libpam-mysql libplrpc-perl mysql-client
mysql-client-5.1 mysql-common mysql-server mysql-server-5.1 mysql-server-core-5.1 ssl-cert update-inetd vsftpd
0 aktualizowanych, 17 nowo instalowanych, 0 usuwanych i 25 nieaktualizowanych.
Konieczne pobranie 24,5MB archiwów.
Po tej operacji zostanie dodatkowo użyte 58,2MB miejsca na dysku.

Prawdopodobnie zostaniemy poproszeni o ustawienie hasła do serwera MySQL (o ile nie był już zainstalowany).

Po instalacji niezbędnych pakietów możemy zalogować się do bazy danych i stworzyć bazę naszych użytkowników.

root@iDev:/home/jamzed# mysql -u root -p

Hasło podajemy te, które było ustawione przed chwilą.

Tworzymy strukturę bazy danych:

CREATE DATABASE vsftpd;

GRANT ALL PRIVILEGES ON vsftpd.* TO 'vsftpd'@'localhost' IDENTIFIED BY 'ftpdpass';

USE vsftpd;

CREATE TABLE tb_users (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
login VARCHAR (25) NOT NULL ,
password VARCHAR (41) NOT NULL ,
UNIQUE ( login )
) ;

Powinniśmy już mieć stworzoną zarówno bazę danych vsftpd jak i założoną strukturę tabeli tb_users. Następny krok to przygotowanie serwera vsftpd tak by poprawnie współpracował z bazą danych, serwer ten sam w sobie nie potrafi obsługiwać bazy danych MySQL, ale mamy PAM'a (jest to zestaw bibliotek, które umożliwiają korzystanie z różnych metod autoryzacji w programach), którego własnie wykorzystamy.

Konfiguracja serwera vsftpd znajduje się w pliku: /etc/vsftpd.conf, zróbmy jego kopię i stwórzmy nową własną konfigurację:

root@iDev:/home/jamzed# mv /etc/vsftpd.conf /etc/vsftpd.conf.orig
root@iDev:/home/jamzed# vi /etc/vsftpd.conf

Minimalna konfiguracja naszego serwera:

listen=YES
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
nopriv_user=ftp
chroot_local_user=YES
pam_service_name=vsftpd
guest_enable=YES
guest_username=vsftpd
local_root=/home/vsftpd/$USER
user_sub_token=$USER
virtual_use_local_privs=YES

  • listen = YES - oznacza, że nasz serwer będzie działał jako standalone, jeśli damy NO, serwer uruchomi się dopiero jako daemon z inetd/xinetd
  • anonymous_enable = NO - wyłączamy dostęp użytkownikowi anonymous
  • local_enable = YES - zezwalamy na logowanie się lokalnych użytkowników (passwd)
  • write_enable = YES - zezwalamy na zapis, serwer może przyjmować pliki
  • local_umask = 022 - po załadowaniu pliku ustawiane będą uprawnienia 755
  • dirmessage_enable = YES = włączamy powitanie użytkowników (plik podawany określamy poprzez message_file)
  • xferlog_enable = YES - włączamy format logowania wu-ftpd
  • connect_from_port_20 = YES - włączamy połączenia aktywne
  • nopriv_user = ftp - nazwa użytkownika używanego przez vsftpd w momencie kiedy chce zrzucić uprawnienia
  • chroot_local_user = YES - chrootuj użytkowników lokalnych (użytkownik nie wyjdzie wyżej do katalogu niż jego HOME)
  • pam_service_name = vsftpd - nazwa usługi podczas autoryzacji pam.d (/etc/pam.d/vsftpd)
  • guest_enable = YES - włączamy konto gościa
  • guest_username = vsftpd - będzie nim użytkownik vsftpd
  • local_root = /home/vsftpd/$USER - określamy katalog do którego vsftpd będzie robił chdir()
  • user_sub_token = $USER - chrootujemy użytkowników do ich katalogów (przydatne dla wirtualnych użytkowników)
  • virtual_use_local_privs = YES - włączamy identyczne traktowanie użytkowników wirtualnych jak i lokalnych

Przejdźmy teraz do konfiguracji pam.d:

root@iDev:/home/jamzed# mv /etc/pam.d/vsftpd /etc/pam.d/vsftpd.orig
root@iDev:/home/jamzed# vi /etc/pam.d/vsftpd

Minimalna zawartość pam.d/vsftpd:

auth required pam_mysql.so user=vsftpd passwd=ftpdpass host=localhost db=vsftpd table=tb_users usercolumn=login passwdcolumn=password crypt=2
account required pam_mysql.so user=vsftpd passwd=ftpdpass host=localhost db=vsftpd table=tb_users usercolumn=login passwdcolumn=password crypt=2

Pozostaje dodanie użytkownika vsftpd:

# useradd -d /home/vsftpd -g nogroup -m -s /bin/false vsftpd

Po tych zmianach, możemy wystartować/zrestartować nasz serwer vsftpd:

root@iDev:/home/jamzed# /etc/init.d/vsftpd restart

Przed założeniem użytkownika w bazie załóżmy strukturę katalogów na filesystemie i ustawmy uprawnienia:

root@iDev:/home/jamzed# mkdir /home/vsftpd
root@iDev:/home/jamzed# mkdir /home/vsftpd/jamzed
root@iDev:/home/jamzed# chown -R vsftpd:nogroup /home/vsftpd/

Teraz możemy już założyć użytkownika w bazie:

root@iDev:/home/jamzed# mysql -uvsftpd -pftpdpass vsftpd
mysql> INSERT INTO tb_users VALUES('','jamzed',PASSWORD('eftep'));

dla sprawdzenia wykonajmy:

mysql> select * from tb_users;
+----+--------+-------------------------------------------+
| id | login  | password                                  |
+----+--------+-------------------------------------------+
|  1 | jamzed | *483A665E1F29FE18A8DD3C658F812A9FC13C1104 |
+----+--------+-------------------------------------------+
1 row in set (0,00 sec)

Pozostaje generalny test, czyli zalogowanie się:

jamzed@makufka:~$ ncftp -ujamzed 192.168.1.194
NcFTP 3.2.2 (Sep 04, 2008) by Mike Gleason (http://www.NcFTP.com/contact/).
Connecting to 192.168.1.194...
(vsFTPd 2.2.0)
Logging in...
Password requested by 192.168.1.194 for user "jamzed".

Please specify the password.

Password: *****

Login successful.
Logged in to 192.168.1.194.
ncftp / > ls -la
drwxr-xr-x    2 105      114          4096 Mar 02 20:15 .
drwxr-xr-x    2 105      114          4096 Mar 02 20:15 ..
ncftp / >

Wszystko poszło bez problemów, nasz serwer FTPd współpracuje z bazą danych, pozostaje już tylko stworzyć formatkę do zmiany haseł i problem z telefonami znika.

PS. Dziękuję Rafałowi 'gwn' za zgłoszenie błędu w konfiguracji (brak użytkownika vsftpd w systemie oraz włączenie konta gościa).

IPSet - znany i nieznany


== Sytuacja: ==
Masz firewall/router oparty o netfilter/iptables. Filtrujesz ruch pomiędzy wieloma stacjami, do tego masz masę vlanów, róźne klasy adresowe, sieci przyłączone itd itp - liczba oraz poziom skomplikowania twoich reguł rośnie wykładniczo a co za tym idzie wydajność twojego fw/routera przy dużym ruchu spada.

Czytaj dalej

VMware ESXi, odblokowanie SSH

Każdy *nix admin chciałby mieć dostęp via ssh do swojej kochanej maszynki :)
Jako że, Vmware ESXi to nic innego jak "delikatnie" przerobiony linux taki dostęp jest również możliwy aby go uaktywnić należy posiadać dostęp fizyczny do maszyny na której on stoi jak i wykonać kilka prostych akcji:

* Przechodzimy na pierwszą konsole (ALT + F1)
* Wpisujemy słówko **unsupported** i klikamy w enter ( na ekranie nic się nie wyświetli, ale zadziała musicie wierzyć na słowo :)
* System poprosi nas o hasło root-a , które oczywiście mu podajemy :). Jeśli wszystko pójdzie ok to uzyskamy dostęp do konsoli
* Następnym krokiem będzie edycja pliku /etc/inetd.conf/ w którym odnajdujemy linijke poniżej i usuwamy komentarz:

# ssh stream tcp nowait root /sbin/dropbearmulti dropbear ++min=0,swap,group=shell -i -K60

* Plik oczywiście zapisujemy, i kolejną i ostatnia rzeczą która trzeba zrobić to **restart daemona inetd**
~ # ps aux|grep inetd
5043 5043 busybox inetd
~ # kill -9 5043
~ # inetd
~ #

I już teraz możemy się cieszyć bezpośrednim dostępem do konsoli po ssh która może się przydać do wielu ciekawych rzeczy ale o tym później

VMware ESXi 4.0, instalacja na pendrive

Na wstępie artykułu chciałem nadmienić, że nie jestem ani nie uważam się za ESXi-owego specjalistę, jednak postanowiłem na tle swojego krótkiego doświadczenia opisać jak przebiega instalacja Hosta z wykorzystaniem pendriva

Przed przystąpieniem do zabawy z esx-em należy mieć świadomość tego, że ESXi obsługuje bardzo ograniczoną liste sprzętu z którym współpracuje i tutaj przykładowo jeden z bardziej popularnych chipsetów karty sieciowej jakim jest Realtek w ogóle nie jest obsługiwany, jaki ESXi w wersji 4.0 działa TYLKO z komputerami wspierającymi architekturę 64bit listę kompatybilnego sprzętu można podejrzeć poniżej: http://www.vmware.com/resources/compatibility/search.php

Instalacja na Pendrive
Najprostszym sposobem na instalacje Hosta ESX-i jest instalacja go na pendrive (wystarczy 1GB), aby tego dokonać wystarczy pobrać iso instalacyjne ze strony vmware
http://www.vmware.com/products/esxi/
VMware-VMvisor-Installer-4.0.0.Update01-208167.x86_64.iso

Kolejną rzeczą która trzeba zrobić to wgranie obrazu systemu na PenDrive
montujemy obraz iso po loopie :)

mount -o loop VMware-VMvisor-Installer 4.0.0.Update01-208167.x86_64.iso /mnt/cdrom/

wchodzimy do /mnt/cdrom robimy unpack na image.tgz
mkdir /tmp/vmware
tar -zxvf image.tgz -C /tmp/vmware
usr/
usr/lib/
usr/lib/vmware/
usr/lib/vmware/installer/
usr/lib/vmware/installer/VMware-VMvisor-big-208167-x86_64.dd.bz2

Plik VMware-VMvisor-big-208167-x86_64.dd za pomoca dd wrzucamy na pendriva w moim przypadku urządzenie sdc

dd if=VMware-VMvisor-big-208167-x86_64.dd of=/dev/sdc

Gratuluje :) w tym momencie wystarczy ustawić bootowanie maszyny z USB i wystartuje nam HyperVisor ESX-a

Zabezpieczamy nasz kontent przed hotlinkowaniem

Czym jest hotlinkowanie? Najprostsze wytłumaczenie tego popularnego zjawiska to osadzanie elementów (grafika, skrypty js, css) na swojej stronie linkując je bezpośrednio z innej. Bardzo często możemy spotkać się z takim procederem na aukcjach Allegro, gdzie sprzedający umieszcza zdjęcia sprzedawanego przedmiotu podlinkowane bepośrednio z zewnętrznej strony, której właścicielem nie jest.

Czy takie hotlinkowanie może nieść ze sobą jakieś niebezpieczeństwa? Oczywiście, że tak. Przede wszystkim zarówno dla holinkującego jak hotlinkowanego. Hotlinkowany traci wykupione pasmo w usłudze hostingu, jego elementy strony są wykorzystywane na zewnętrznych serwisach bez jego wiedzy, a tego przecież nikt nie chce. Hotlinkujący natomiast, może któregoś dnia zamiast zdjęć przedmiotu na swojej aukcji zobaczyć treści obraźliwe, lub żartobliwe (w najlepszym wypadku), taki sposób zniechęcania hotlinkujących jest niezwykle skuteczny, chociaż powiedziałbym, agresywny.

Jak możemy się zabezpieczyć? Możemy wykorzystać kilka metod, najprostsza z nich to weryfikowanie nagłówka HTTP REFERER wysyłanego przez przeglądarkę przy każdym requeście. Metoda ta opiera się w pełni na wykorzystaniu właściwości protokołu HTTP, który nakazuje umieszczać adres strony z której odwołujemy się do obiektu, adres ten jest umieszczany w nagłówku REFERER. Jeśli wykorzystujemy serwer Apache do serwowania naszych stron, możemy stworzyć konkretne reguły Rewrite (mod_rewrite) i sprawdzać, czy dane żądanie przychodzi z Refererem zawierającym naszą stronę, jeśli tak to obiekt jest podawany, jeśli Referer zawiera inny adres to zwracamy odpowiedź z kodem 403 (forbidden).

Przykładowy plik .htaccess

RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www.)?naszastrona.pl/ [NC]
RewriteRule .(gif|jpg|png)$ - [F]

  • nasz HTTP_REFERER nie jest pusty
  • nasz HTTP_REFERER jest różny od http://naszastrona.pl lub http://www.naszastrona.pl/
  • jeśli żądanie dot. plików gif, jpg, png - zablokuj takie żądanie [F]

Czyli jeśli ktoś podlinkuje obiekty graficzne na swojej stronie bezpośrednio z naszej to taki request zostanie wyłapany i zablokowany.

W przypadku serwera lighttpd możemy podmienić obrazek na inny również wykorzystując zestaw reguł mod_rewrite:

Przykład podmiany obrazka w przypadku wykrycia hotlinkowania:

$HTTP["referer"] =~ ".*naszastrona.pl.*" {
url.rewrite = ("(?i)(/.*.(gif|jpg|png))$" => "/anty_hotlink.png" )
}

  • anty_hotlink.png - jest obrazkiem który wyświetla się hotlinkującemu zamiast oryginalnej grafiki.

Zupełnie innym sposobem zniechęcającym do hotlinkowania jest blokowanie zasobów poprzez generowanie tymczasowych adresów do obiektów. Gotowym takim rozwiązaniem jest moduł  mod_secdownload, zaimplementowany w serwerze lighttpd. Zasada działania tego modułu opiera się o generowanie adresów do konkretnych obiektów w oparciu o klucz i czas (wymagane jest napisanie prostego skryptu PHP). Adres do konkretnych zasobów będzie zawierał unikalny, dodatkowy ciąg znaków, który po przekroczeniu limitu czasowego zmieni się i dotychczasowy URL przestanie być aktywny.

Przykładowy skrypt PHP do generowania URLi:

<?php
$secret = "klucz";
$uri_prefix = "/pliki/";
$f = "/tajny.txt";
$t = time();$t_hex = sprintf("%08x", $t);
$m = md5($secret.$f.$t_hex);
printf('<a href="%s%s/%s%s">%s</a>',
$uri_prefix, $m, $t_hex, $f, $f);
?>

Nasz URL to teraz md5-składanka z czasu, nazwy pliku oraz słowa klucza, wygląda mniej więcej tak: http://serwer/pliki/db66551e129600d9b4483d223dea8a9e/4aae9666/tajny.txt, sama konfiguracja modułu wygląda następująco:

secdownload.secret          = "klucz"
secdownload.document-root   = "/Site/katalog_z_plikami"
secdownload.uri-prefix      = "/pliki/"
secdownload.timeout         = 10

  • secdownload.document-root - prawdziwy katalog z plikami,
  • secdownload.uri-prefix - pseudo katalog, tak naprawdę istnieje tylko w URI,
  • secdownload.timeout - czas ważności URL’a,

W przypadku kiedy mamy np. flashowy player pobierający jakąś mp3 i request generowany przez flash będzie praktycznie chwilę po wygenerowaniu URL’a timeout taki powinien być możliwie niski (2 sek).

Wdrożenie powyższych metod nie powinno zająć dużo czasu administratorowi stron WWW, a rezultat może przynieść miłe zaskoczenie, także DO BOJU! ;-)

nginx+memcached - cache szybkozmienny

nginx to lekki serwer WWW (i proxy) zaprojektowany z myślą o wysokiej wydajności, przy zachowaniu niskich wymagań pamięciowych.

memcached to system cachowania obiektów w pamięci, udostępniający API dla wielu języków. Dane przechowywane są jako pary klucz-wartość i dostępne dla każdego, bez autoryzacji. Z założenia dane w memcache nie są trwałe, są tracone w momencie restartu procesu lub timeoutu danego klucza.

W przypadku serwisów generujących duże ilości requestów, które są z zasady tymczasowe, a wymagają narzutu na wygenerowanie (np. xmle, serializowane dane z bazy w jsonie, itp.), można zastosować kombinację nginx i memcached, dla osiągnięcia znacznego wzrostu wydajności i zmniejszenia obciążenia bazy.

NginxHttpMemcachedModule pozwala na użycie jako backendu memcache, przekazywanie uri jako klucza i serwowanie odpowiedzi z cache'u. Niestety moduł nie obsługuje setów, więc konieczne jest napisanie sobie skryptu wypełniającego cache obiektami (np. w cronie co kilka minut), ustawiając czas expirowania kluczy dłuższy niż częstotliwość wypełniania.

Do interfejsowania memcache w PHP służy PECL::Package::memcache, natomiast w Perlu na przykład Cache::Memcached. Przygotowując klucze z poziomu PHP należy pamiętać o wyłączeniu Memcached::OPT_COMPRESSION, ponieważ wartości w memcached powinny być niegzipowane.