2015. december 8., kedd

[SOLVED] Cent OS 7 ImportError: No module named cfnbootstrap

If you are trying to use Cloudformation init on CentOS7 or RedHat7, you may meet the following issue:
# /opt/aws/bin/cfn-init 
Traceback (most recent call last):
  File "/opt/aws/bin/cfn-init", line 19, in <module>
    import cfnbootstrap
ImportError: No module named cfnbootstrap

Solution:
Validate, the cfnbootstrap is available by:
find / -name cfnbootstrap
/usr/lib/python2.7/dist-packages/cfnbootstrap

It means, the module is installed, but during import, python can not find it. 
Let's check the PYTHONPATH parameter, which defines the folders where to check for modules.

This is a simple way to list the directories:
# python2.7
Python 2.7.5 (default, Jun 24 2015, 00:41:19) 
[GCC 4.8.3 20140911 (Red Hat 4.8.3-9)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> /dist-packages
KeyboardInterrupt
>>> import sys
>>> print '\n'.join(sys.path)

/usr/lib64/python2.7/site-packages/gevent-1.0.1-py2.7-linux-x86_64.egg
/usr/lib/python2.7/site-packages/boto-2.36.0-py2.7.egg
/usr/lib/python2.7/site-packages/python_binary_memcached-0.24-py2.7.egg
/usr/lib64/python27.zip
/usr/lib64/python2.7
/usr/lib64/python2.7/plat-linux2
/usr/lib64/python2.7/lib-tk
/usr/lib64/python2.7/lib-old
/usr/lib64/python2.7/lib-dynload
/usr/lib64/python2.7/site-packages
/usr/lib/python2.7/site-packages
>>> 
As you can see, the module's containing folder (/usr/lib/python2.7/dist-packages/) is not listed here.

Now you have two options to solve this:
  1. Create a symbolic linc between the folders:
    ln -s /usr/lib/python2.7/dist-packages/cfnbootstrap /usr/lib/python2.7/site-packages/cfnbootstrap 
  2. Extend the PYTHONPATH parameter by adding the following line to /etc/environments:
    export PYTHONPATH="${PYTHONPATH}: /usr/lib/python2.7/dist-packages/"

I hope it helps.

2015. november 8., vasárnap

Scripting LVM Snapshot Backups

In this article I will show you how you can simply backup an LV.
In this example we are going to script a mail server's mailbox store LVM backup.

The LVM backup script


#!/bin/bash
SIZE=1G            # Size of a snapshor. Make sure, there is space for all 7 snapshot
VGNAME=/dev/vg00    # /dev/VGNAME
LVTOBACKUP=www        # only the name of LV
DAY=`date +%a`
SNAPSHOTNAME="$LVTOBACKUP-$DAY"

# ---------------------- Do not edit under this line  -------------------

SCRIPTNAME=$0
SENDMONITORINGDATA=true
FAILOCCURED=0
# Detailed log to syslog
#exec > >(logger -t "$SCRIPTNAME" -p local3.info ) 2> >(logger -t "$SCRIPTNAME" -p local3.info)
set -x
ZBXSENDER=`which zabbix_sender`
if [ $? -ne 0 ]
then
    SENDMONITORINGDATA=false
    echo "Not sending data to Zabbix"
fi
ZBXCONFIG=`find /etc/zabbix/ -maxdepth 1  -name '*.conf' | tail -n 1`
sendMonitoringData(){
    if [ "$SENDMONITORINGDATA" = true ]
    then
        echo "Sending monitoring data: $SCRIPTNAME - $1"
        $ZBXSENDER --config $ZBXCONFIG --key scripterror --value $1
        $ZBXSENDER --config $ZBXCONFIG --key scriptname --value $SCRIPTNAME
    fi
}
checkResult(){
    EXITSTATUS="$1"
    shift
    MSG="$*"
    if [ $EXITSTATUS -eq 0 ]
    then
        echo "$MSG - OK"
    else
        echo "$MSG - FAIL"
        sendMonitoringData $EXITSTATUS
        FAILOCCURED=1
    fi
}
lvs "$VGNAME/$SNAPSHOTNAME" 2>&1 > /dev/null
RETCODE=$?
checkResult $RETCODE "Failed to check $VGNAME/$SNAPSHOTNAME"
if [ $RETCODE -eq 0 ]
then
    lvremove -f "$VGNAME/$SNAPSHOTNAME"
    checkResult $? "Failed to remove $VGNAME/$SNAPSHOTNAME"
fi
sync
lvcreate -L "$SIZE" -s -n "$SNAPSHOTNAME" "$VGNAME/$LVTOBACKUP"
checkResult $?  "Failed to create $VGNAME/$SNAPSHOTNAME"
if [ $FAILOCCURED -eq 0 ]
then
    sendMonitoringData 0
else
    echo "Due to failure no more alert to monitoring"
fi


Explanation


It seems a bit complicated script, let me explain:

LVM part

The basic backup script looks like this
 #!/bin/bash
SIZE=1G            # Size of a snapshot. Make sure, there is space for all 7 snapshot
VGNAME=/dev/vg00    # /dev/VGNAME
LVTOBACKUP=mail        # only the name of LV
DAY=`date +%a`
SNAPSHOTNAME="$LVTOBACKUP-$DAY"

# ---- 
lvs "$VGNAME/$SNAPSHOTNAME" 2>&1 > /dev/null
RETCODE=$?
if [ $RETCODE -eq 0 ]
then
    lvremove -f "$VGNAME/$SNAPSHOTNAME"
fi
sync
lvcreate -L "$SIZE" -s -n "$SNAPSHOTNAME" "$VGNAME/$LVTOBACKUP"


For LVM snapshot you need to define the snapshot Logical Volume size.
It depends on the application witch data you are backing up.
After the initialization period you can check the used amount of Snapshot space like this:
# lvs
  LV       VG   Attr   LSize  Origin Snap%  Move Log Copy%  Convert
  mysql    vg00 -wi-ao  5,00g                                     
  swap     vg00 -wi-ao  4,00g                                     
  mail     vg00 owi-ao 30,00g                                     
  mail-Sun vg00 swi-a-  1,00g mail    0,70

You can see that, it uses the 0.7% of the configured size. I could configure smaller size...

I'm using daily backup, it creates a new LV snapshot every day. The name is came from the original Logical Volume's name and the sort version of day of week.

The script first check whether the last weeks LVM snapshot is available. If exist, then first it removes that.
Then the last lines are coming:
sync
lvcreate -L "$SIZE" -s -n "$SNAPSHOTNAME" "$VGNAME/$LVTOBACKUP"

Sync is important, because we need the data on disk, not in cache ;-)

Monitoring

You can see that, there are more lines connecting to monitoring than for the backup.
To make sure, this script can be used on different distributions and different versions, the path are not burned into the code.
Every step of the backup process is monitored and reported to Zabbix and of course to syslog.

Please feel free to use it.

All feedback are welcome.

2015. szeptember 28., hétfő

[SOLVED] Python Cannot assign requested address

Python exception

Traceback (most recent call last):
  File "/usr/lib/python2.7/multiprocessing/process.py", line 258, in _bootstrap
    self.run()
  File "/srv/sbproxy/sandboxproxytest/framework/multiprocess.py", line 39, in run
    res = self._function(*task)
.
.
.
    return requests.request(method, url, **kwargs)
  File "/usr/lib/python2.7/dist-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/lib/python2.7/dist-packages/requests/sessions.py", line 455, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/lib/python2.7/dist-packages/requests/sessions.py", line 558, in send
    r = adapter.send(request, **kwargs)
  File "/usr/lib/python2.7/dist-packages/requests/adapters.py", line 378, in send
    raise ConnectionError(e)
ConnectionError: HTTPConnectionPool(host='172.31.37.5', port=8080): Max retries exceeded with url: /jobs/list?after=2015-09-27 (Caused by <class 'socket.error'>: [Errno 99] Cannot assign requested address)


Check netstat | grep -c tcp 
When the expection happens, the above commans show around 28000.
Reason can be seen here:
http://stackoverflow.com/questions/11190595/repeated-post-request-is-causing-error-socket-error-99-cannot-assign-reques
If the connections are in CONNECTION_WAIT then the server closed the connection earlier than should, but if the connection is in TIME_WAIT state, that is normal:

RFC 793 sets the TIME-OUT to be twice the Maximum Segment Lifetime, or 2MSL. Since MSL, the maximum time a packet can wander around Internet, is set to 2 minutes, 2MSL is 4 minutes. Since there is no ACK to an ACK, the active closer can't do anything but to wait 4 minutes if it adheres to the TCP/IP protocol correctly, just in case the passive sender has not received the ACK to its FIN (theoretically).

Some explanation:
http://serverfault.com/a/329846

Solution

Set the tcp_tw_recycle to 1
echo 1 >   /proc/sys/net/ipv4/tcp_tw_recycle
Or add to /etc/sysctl.conf:
net.ipv4.tcp_tw_recycle = 1
and reload the configuration like this:
sysctl -p 

Some documentation advise to use /proc/sys/net/ipv4/tcp_tw_reuse, but in my case it has not solved this issue.

2015. augusztus 9., vasárnap

Webszerver biztonsági kérdései



Ebben a bejegyzésben néhány általános hibát mutatok be, ami egy webszerver elleni támadásban megkönnyíti a támadó dolgát, de egyszerűen orvosolható lenne.

Sok cég egyszerűen nem fordít figyelmet a webszervere biztonságossá tételére, hiszen "webszervert mindenki tud üzemeltetni".

Lehet úgy csinálni, hogy működjön és lehet úgy is, hogy biztonságosan működjön, jellemzően a "csak működjön" a cél.

Íme a problémák:

Szükségtelen header paraméterek 

Az első szabályok között van a szerver biztonsági szabályok oktatása során, hogy ne áruljuk el, hogy milyen kiszolgáló melyik verziója végzi a szolgáltatást.

Server

Szinte minden webszerver automatikusan hozzáadja a nevét és a verziószámát a HTTP válaszhoz. Ez jó nekik, hiszen jobban szerepelnek az ilyen jellegű felméréseken, viszont egy támadót is kényelmes helyzetbe hoz, mivel kéretlenül átadja a nevét és verziószámát.
Egy nem frissített rendszernél ennél többre nincs is szükség, hiszen a kiszolgáló típusa és verziószáma alapján egyszerűen ki lehet keresni a sérülékenységeit (pl https://www.exploit-db.com/)  és már mehet is a támadás. Ahány webszerver, annyi módja a kikapcsolásnak.

NginX 
Javasolt az nginx-extras csomag használata, mivel ki tudja venni a Server headert:
        more_clear_headers 'Server';
Ettől függetlenül a /etc/nginx/nginx.conf-ban a server_tokens paramétert érdemes off- ra állítani.

Apache

/etc/apache2/conf.d/security fájlban két módosítás javasolt:
 ServerTokens Prod
ServerSignature Off
Ezzel csak a verziót takarjuk el, de a header megmarad, ezért a domain-hez tartozó konfigot érdemes kiegészíteni a következővel:
Header always unset "Server

HAproxy
A HAproxy ugyan nem erre való, de terheléselosztáson kívül is rengeteg dologban tud segíteni. A header-ek manipulálása is hozzátartozik.
A server header eltávolítható vele, a vonatkozó backend szakasz következő sorral való kiegészítésével:
 rspidel ^Server:.*
Érdemes ezt a webszervereknél konfigurálni, ne foglaljuk a loadbalanszert ilyen dolgokkal.

X-Powered-By

Az előzőhöz hasonló céllal jöhetett létre, arról ad információt, hogy a webalkalmazás miben készült, melyik verzió futtatja.

PHP-ban a megfelelő php.ini  expose_php sorát off-ra állítva kapcsolható ki:

expose_php = Off

Amennyiben erre nincs lehetőség, akkor a webszerverre érdemes bízni.

Session ID

Az előzőhöz kapcsolódik. Ha lehet,  azt sem kell a felhasználók orrára kötni, hogy az alkalmazás miben íródott. 

PHP-ben a php.ini session.name paraméter határozza meg, hogy a session cookie milyen névvel jelenjen meg a klienseknél. Ez alapesetben PHPSESSID, érdemes kihagyni a PHP-t a karakterláncból.

Természetesen az URL-ből is kiderülhet, hogy miben írodott az alkalmazás, SEO barát URL-t feltételezünk ebben az esetben...

Hibaoldalak

Webszervertől függően a 404,403, stb státuszokhoz tartozó statikus HTML fájlok is tartalmazzák a webszerver típusát.
A barátságosabb felhasználó értesítésen kívül mégegy ok saját hiba oldalak (Error page) használatára

Szükségtelen modulok

A webszerverek általában előre definiált modul csomaggal érkeznek, amelyek az általános igényeket és a minnél kevesebb konfigurációt tartják  szem előtt. Ennek azonban van hátulütője is, hiszen egy bekapcsolt Apache autoindex modul olyan információkhoz is hozzáférést enged, amihez nem feltétlenül szeretnénk hozzáférést engedni. Pl CMS-ek modules, templates, egyéb könyvtárának listája.
Amennyiben nincs rá szükség, érdemes kikapcsolni.
Apache esetén a2dismod autoindex


PHPmodulok

Nem csak  a webszerverekre érvényesek az előző gondolatok, amire nincs feltétlenül szükség, azt érdemes kikapcsolni.
Egy egyszerű és kényelmes eszköz erre: LAMPSecurityToolkit

Védekezés a feltérképezések ellen

Érdemes belenézni a webszerver logba időnként, hogy lássuk milyen kérések érkeznek, amire a kiszolgálónk 404 - nem található vagy 403 - forbidden üzenettel válaszol.
Pl:
[09/Aug/2015:12:11:35 +0200] "GET /MyAdmin/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:36 +0200] "GET /scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:37 +0200] "GET /db/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:38 +0200] "GET /dbadmin/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:39 +0200] "GET /myadmin/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:39 +0200] "GET /mysql/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:40 +0200] "GET /mysqladmin/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:41 +0200] "GET /phpadmin/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu"
[09/Aug/2015:12:11:42 +0200] "GET /phpMyAdmin/scripts/setup.php HTTP/1.1" 404 56 "-" "ZmEu


 Az ilyen jellegű kéretlen próbálkozások ellen célszerű felokosítani a fail2ban-t, ami az ilyen próbálkozóktól távol tart bennünket. Íme pléda: Apache 404 
Ezzel azért körültekintően kell bánni, hiszen, ha a weboldalunkból hiányzik valamilyen fájl, az tud felesleges kitiltást eredményezni...


Ellenőrzés fájl kiterjesztés szerint

Előfordul, amikor egy webalkalmazáson valamilyen code injection sérülést találnak, de a javításig a szolgáltatást fenn kell tartani.
Pl: Egy sérülékenység eredményeként php fájlokat hoztak létre a támadók egy cache könyvtárban:
# ls -1 cache/*.php
cache/blog27.php
cache/dirs2.php
cache/start65.php
cache/template39.php


Nem kényelmes helyzet, de az alábbi konfigurációval átmenetileg megmenekülünk a spam áradattól (amit feltehetőleg ezek a php fájlok generálnak):

NginX:
location ~ /cache/(.+)\.php$ {
        deny all;
 }

Remélem hasznosnak bizonyulnak a fenti javaslatok, a véleményeket szívesen fogadom.