A Web based system to push your SVN code through development, staging and production environments

Hello there!

In development, having a seamlessly integrated process where you can propagate your code through whatever QA, testing and development policy you have is invaluable and a definite time saver.

We work with SVN as well as GIT code repository systems and have developed a web based system to “Export” or “Push” the code through development, staging and production environments as such.

I have already talked about sanitizing your code during the commit process, to ensure commit messages are standard and there are no PHP fatal errors, so now I will be showcasing you a simple web based system for propagating your code through development, staging and production servers.

This system should be on a secure web accessible page on each server. For the sake of argument , I’ll call each server the following :

dev.server.com — development server

staging.server.com — staging server

www.server.com — production server

We will be using PHP for the web based interface, and we will assume that you will be password protecting access to this page via htpasswd, as well as forcing SSL. I am also assuming that within your SVN repository, you have multiple “sites” that you will be individually pushing or exporting (svn export). Once you have the secure, password protected page (lets call it https://dev.server.com/svn) , the following PHP page will be the main index :

svnupdate.php

<?

$sites[] = array(
"name" => "Site A",
"url" => "http://site-a.server.com",
"path" => "/usr/local/www/site-a.server.com",
"source" => "svn://svn.server.com/repository/branches/site-a",
"login" => "svnlogin",
"base" => "1.00",
"notes" => "Standard build for Site A"
);

"name" => "Site B",
"url" => "http://site-b.server.com",
"path" => "/usr/local/www/site-b.server.com",
"source" => "svn://svn.server.com/repository/branches/site-b",
"login" => "svnlogin",
"base" => "1.00",
"notes" => "Standard build for Site B"
);

?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
        <title>SVN Update Page</title>

<style>
body {
        background-color:#eeeeee;
}

.tdheader {
        background-color:#0f2c66;
        color:#FFFFFF;
        font-weight: bold;
}

.tdheader2 {
        background-color:#000000;
        color:#FFFFFF;
        font-weight: bold;
}

.tdrow {
        background-color:#ffffff;
        color:#000000;
        font-weight: normal;
}

a:link,a:active,a:visited {
        color:#0f2c66;
}

a:hover {
        color:#6A9CD3;
}
.menuon {
        background-color:#6699cc;
        color:white;
        font-weight: bold;
}

.menuoff {
        background-color:white;
        color:black;
        font-weight: bold;
}

table {
        border-style: solid;
        border-width: 1px;
        border-color: #000000;
}

</style>
<script type="text/javascript">
function confirmexport(text) {
        if (confirm(text)) {
                document.getElementById('framecont').style.display = '';
                document.getElementById("processframe").contentWindow.document.body.innerHTML = "<div align='center'>Exporting...</div>";
                return true;
        } else return false;
}
function viewframe() {
        document.getElementById('framecont').style.display = '';
}
function closeframe() {
        document.getElementById('framecont').style.display = 'none';
}

</script>
</head>
<body>
<table width="750px" cellpadding="2" cellspacing="1" bgcolor="#000000" border="0">
<tr>
<td class="tdheader2">Server: </td>
<td class="menuon" align="center">Development Server</td>
<td class="menuoff" align="center"><a href="https://staging.server.com/svn/svnupdate.php">Staging Server</a></td>
<td class="menuoff" align="center"><a href="https://www.server.com/svn/svnupdate.php">Production Server</a></td>
</tr>
</table>

<hr size="1" noshade="noshade" />
<a href="log.php" target="processframe" onclick="closeframe();viewframe()">View Development Export Log</a>
<br><br>
<table cellpadding="2" width="1000px" cellspacing="1" bgcolor="#000000" border="0">
<tr class="tdheader">
<td>Site</td>
<td>Source</td>
<td>UN/PW</td>
<td>Base</td>
<td>Revision</td>
<td>Export</td>
<td>Pending Updates</td>
<td>Notes</td>
</tr>

<?
if($sites) {
foreach($sites as $key => $value) {
?>
<form method="post" action="svnupdate_process.php" target="processframe">
<tr class="tdrow">
<td><a href="<?=$value['url']?>" target="_blank"><?=$value['name']?></a></td>
<td><?=preg_replace("/svn:\/\/svn\.server\.com\//","",$value['source'])?><input type="hidden" name="source" value="<?=$value['source']?>"></td>
<td><?=$value['login']?></td>
<td><?=$value['base']?></td>
<td><input type="text" name="revision" size="5"></td>
<td><input type="hidden" name="site" value="<?=$value['path']?>">
<input type="submit" name="submitbutton" value="Export" onClick="javascript:return confirmexport('This will overwrite the current files on development. Are you sure?');">
</td>
<td width="150px"><center><a href="viewcommit.php?name=<?=$value['name']?>&path=<?=$value['path']?>&svn=<?=$value['source']?>" target="processframe" onclick="closeframe();viewframe()">View</a></center></td>
<td><?=$value['notes']?></td>
</tr>
</form>
<? } ?>
<? } ?>
</table>
<br><div id='framecont' style="text-align: left; display: none">
<iframe name="processframe" id="processframe" width="1000px" height="300px" align="left" scrolling="yes" frameborder="0">
                </iframe>
</div>
</body>
</html>

If you carefully look at the above code, you will see that this page will be dependent on 3 external scripts, which I will describe below. The page itself generates a list of whatever sites you want to include in the push process, within a PHP based Array. The array details important info per site such as the name, svn location, location of the files on the server as well as whatever other notes and additional info you want to provide.

Each time a site is “exported” by clicking the export button, it calls an external script called svnupdate_process.php. This executes the SVN EXPORT command, as well as logging which user requested the action into a simple text based log file. The user is determined by the authentication user that is accessing the page. The htpassword credentials you will be providing to your users should be set per-user so that it can be easier to determine who pushed the code and whatnot.

The other two external scripts are one that will view the log file in an iframe on the same page, as well as a script to extrapolate the pending commits that are in the queue since the LAST code push / svn export. That is really useful, as you can imagine.

Script to view the export log

This script, log.php is used to dump the contents of the log.txt export log file. Very simple

log.php

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
        <title>Untitled</title>
</head>

<body>
<b>Development Export Log:</b><hr>
<?
include("./functions.inc.php");
$logfile = new logfile();
$logfile->display();
?>
</body>
</html>

Simple, right? The log.php code includes a functions.inc.php file, used for writing and reading the log.txt file. The above code depends on it, as well as the svnupdate_process.php code (described below), in order to log each time someone hits the export code button

functions.inc.php

<?
class logfile {
        function logfile() {
                $this->filename = "log.txt";
                $this->Username = $_SERVER['PHP_AUTH_USER'];
                $this->logfile = $this->filename;
        }

        function write($data) { // write to logfile
                $handle = fopen($this->logfile, "a+");
                $date = date("Y-m-d H:i:s");
                $IP = getenv('REMOTE_ADDR');
                $data = "[$date] {$this->Username}:{$IP} - " . $data . "\n";
                $return = fwrite($handle, $data);
                fclose($handle);
        }

        function display() { // display logfile
                $handle = fopen($this->logfile, "a+");
                while(!feof($handle)) { // Pull lines into array
                        $lines[] = fgets($handle, 1024);
                }
                $count = count($lines);
                $count = $count - 2;
                for($i=$count;$i>=0;$i--) {
                        echo $lines[$i] . "<br>";
                }
                fclose($handle);
        }
}
?>

The code of the svn export process is handled by the following script below. Again its self explanatory. PHP executes a shell command to export the svn code based on the array variables defined in the very first script. Make sure all the variables match up to whats in svn and the location of the files, and even execute a test run of the command manually with the variables. If there are problems, you can modify the command to pipe the output to a log file for further analysis. Additionally you may need to alter the permissions of the apache user so that the command can be properly run. Avoid setting the apache user to have a shell (big no-no) ,but maybe a nologin shell or something along those lines. Its completely up to you , but be very careful about the choices you make to get it to run properly.

svnupdate_process.php

<b>Update/Status Window</b>
<hr>


<?
include("./functions.inc.php");
$logfile = new logfile();

if($_POST['submitbutton']) {

        if($_POST['revision'] != "") {
                $revision = "-r ".$_POST['revision'];
        }

        $command = "/usr/bin/svn export --force --username svnuser --password 'svnpassword' $revision --config-dir /tmp ".$_POST['source']. " " . $_POST['site']." 2>&1";

        if($_POST['submitbutton'] == "Export") {
                $output = shell_exec("umask 022;".$command);
        }

        echo "<pre>$output</pre>";
        $logtext = "Exported to {$_POST['site']}";
        $logfile->write($logtext);
        eaccelerator_clear();
}

?>

Finally the last script will be the script that parses the SVN log output with a date/time range from the last time the export button was pushed, until the current date and time. This will load the output in the same iframe log window on the svn page so the user can see what pending commits are in the code since the last time it was exported. Invaluable information, right?

Note that this has a function to filter out additional illegal characters to avoid cross site scripting injections. This code should be completely 100% restricted from outside public use, however it might be worth it to put this function in the svnupdate_process.php script as well. Can’t be too careful. I thought I’d include it here for you to use.

viewcommit.php

<?

        if(($_GET['svn'] != "") && ($_GET['path'] != "") && ($_GET['name'] != "")) {

        // Cross Site Script  & Code Injection Sanitization
        function xss_cleaner($input_str) {
        $return_str = str_replace( array('<',';','|','&','>',"'",'"',')','('), array('&lt;','&#58;','&#124;','&#38;','&gt;','&apos;','&#x22;','&#x29;','&#x28;'), $input_str );
        $return_str = str_ireplace( '%3Cscript', '', $return_str );
        return $return_str;
        }

        $xss_path=xss_cleaner($_GET['path']);
        $xss_svn=xss_cleaner($_GET['svn']);
        $xss_name=xss_cleaner($_GET['name']);

        echo "<b>Viewing Pending Updates For : ". $xss_name . "</b>";
        echo "<hr>";

        $command = "/usr/bin/svn --username svnuser --password 'svnpassword' --config-dir /tmp log " . $xss_svn . " -r {\"`grep \"" . $xss_path . "\" log.txt | tail -n 1 | awk -F \" \" '{printf \"%s %s\", $1,$2}' | sed -e 's/\[//g' -e 's/\]//g'`\"}:{\"`date \"+%Y-%m-%d %H:%M:%S\"`\"}";

        $output = shell_exec("umask 022;".$command);
        echo "<pre>$output</pre>";
}
        else {
        echo "No queries passed!";
        }

?>

Lets break down the SVN log command, so you know whats going on. I’m grabbing the SVN site array variables when the “view log” link is clicked on the svn page. I am also parsing the export log text file to get the last entry for the particular site in question, grabbing the date and time.

I am then getting the current date and time to complete the date/time range in the svn log query. The finished query should look something like this :

svn --username svnuser --password 'svnpassword' --config-dir /tmp log svn://svn.server.com -r {"2013-01-01 12:01:00"}:{"2013-02-01 12:01:00"}

How to detect and mitigate DoS (Denial of Service) Attacks

Greetings,

Occasionally with a very busy site, being behind a hefty web stack does not always have enough capacity to mitigate a significant surge in artificial (DoS) requests. Detecting and mitigating denial of service attacks is an important and time sensitive exercise that will determine the next mitigating steps that you may need to take to reduce or null route the offending traffic.

These steps are very basic and use the everyday system utilities and tools that are found in vanilla linux implementations. The idea is to utilize these tools to identify connection and request patterns.

I’m going to assume that your potential or assumed attack is going straight to port 80 (HTTP), which would be a common assumption. A typical DoS attack would just be a generation of thousands of requests to a particular page, or perhaps just to the homepage.

Check your Process and Connection Counts

It is a good idea to get a picture of how overworked your system is currently, other than the initial reports of slow site performance or perhaps mysql errors such as “The MySQL server has gone away”, or anything of the sort.

Count how many apache/httpd processes you currently have to get an idea :

# ps auxw | grep httpd | wc -l
96

Check what the CPU load is currently on the server :

# w
 15:41:52 up 74 days, 17:05,  1 user,  load average: 6.36, 9.89, 8.68

So you can see the load is quite high and there are 96 apache processes that have spawned. Looks to be quite a busy server! You should take it a step further and perhaps identify how many open port 80 (HTTP) connections you have :

# netstat -nap | grep ":80 " | wc -l
1627

So thats quite a significant number of HTTP connections on this server. It could be a substantial DoS attack, especially when you consider that this may be one server in a 3 server load balanced pool. That means the numbers here are likely multiplied by three.

Still, it could be legitimate traffic! The spike could be attributed to a link on reddit, or perhaps the site was mentioned on a popular news site. We need to look further at what requests are coming in to be able to determine if perhaps the traffic is organic or artificial. Artificial traffic would typically have thousands and thousands of identical requests, possibly coming from a series of IP addresses.

How distributed a DoS attack probably depends on the skill and resources of the offending party (potentially). If its a DoS, hopefully it will be easily identifiable.

Lets take a closer look at the open connections. Maybe we can see how many connections from singular IP addresses are currently open on the server. That may help identify if the majority of the traffic is from a single or single set of sources. This information can be kept aside after our analysis is complete so that we can use that information to report and block the traffic to ultimately mitigate the attack.

# /bin/netstat -ntu | grep ":80" | awk '{print $5}'| cut -d: -f1 | sort | uniq -c | sort -n | \
grep -v 127.0.0.1 | awk '{if ($1 > 45) print $2;}'

What the above line essentially does is scan the open connections specifically to port 80 and filters only the IP addresses that have 45 or more open connections. This number can obviously be adjusted to whatever you like. Take a look at the different results and see what it produces.

For potentially offending IP addresses, whois them and see where they are originating from. Are they from a Country that typically isn’t interested in your site? If the site is an English language site about local school programs in a small North American city, chances are someone from China or Russia has little legitimate interest in the site.

Analyze the Requests

In this scenario, we would be analyzing the apache access logs in order to get a clearer picture of what exactly is happening that is generating the DoS. Access logs in apache are a great resource to get the source IP, request URI and other useful information that may help identify an automated tool such as LOIC or an automated botnet perhaps that is sending thousands of identical requests to your server.

Lets filter our the actual GET requests from the apache logs, sort them and count each request in order to show the highest number of requests for the same URI. If we can take this information and then cross reference it with the connection stats we gathered earlier, we should have a clear picture of who is conducting the attack and how they are doing it.

cat access_log | awk -F "\"" '{printf "%s\n", $2}' | sed -e 's/GET //' | awk -F " " '{printf "%s\n" ,$1}' | sort | uniq -c | sort -n | awk '{if ($1 > 45) print $2;}' | more

This code filters GET requests from the logs, cleans them up, sorts the results, counts all the duplicate requests , sorts it by highest number of requests, and prints the results.

Again the 45 in the last portion of the command can be changed to whatever you feel is necessary. It all depends on whats a normal request, what is legitimate traffic and how busy your server is normally.

All the data you have gathered thus far should be enough to complete a preliminary investigation into your DoS attack. As for mitigating , there are many options. I wont go too much into it because that could be considered a completely separate topic. I’ll give some suggestions for you, either way :

Block offending IPs with IPTABLES :

iptables -A INPUT -s 1.2.3.4 -j DROP

Use software layer mitigation such as mod_evasive or mod_security to reduce the ability of attackers to generate significant numbers of requests. Most importantly of all, use your best judgement!

SVN Offsite Backup Script : Secure offsite backup solution for SVN to Amazon S3

Hi there!

Backing up your code repository is important. Backing up your code repository to an off-site location in a secure manner is imperative. Throughout our travels and experience utilizing the SVN code repository system, we have developed a quick bash script to export the entire SVN repository, encrypt it, compress it into an archive, and then ship it (over an encrypted network connection) to Amazon S3 storage.

We will be using the (familiar) s3sync Ruby script to do the actual transport to Amazon S3, which you can find here.

Note also that this script also keeps a local copy of the backups, taken each day, for a maximum of 7 days of retention. This might be redundant since all revisions are kept within SVN itself, but I thought it would provide an additional layer of backup redundancy. The script can be easily modified to only backup a single file every night, overwriting the older copy after every backup.

Here’s the script :

#!/bin/sh
# SVN Off Site Backup script
# www.stardothosting.com

currentmonth=`date "+%Y-%m-%d %H:%M:%S"`
threedays=`date -v-5d "+%Y-%m-%d"`
todaysdate=`date "+%Y-%m-%d"`

export AWS_ACCESS_KEY_ID="YOUR-S3-KEY-ID"
export AWS_SECRET_ACCESS_KEY="YOUR-S3-ACCESS-KEY"


echo "SVN Offsite Backup Log: " $currentmonth > /var/log/offsite-backup.log
echo -e "--------------------------------------------" >> /var/log/offsite-backup.log
echo -e "" >> /var/log/offsite-backup.log

# Archive Repository Dump Files and remove files older than 7 days
/usr/bin/find /subversion/svn_backups -type f -mtime +7 -delete

# Backup SVN and encrypt it
svnadmin dump /subversion/repo_name | /usr/bin/openssl enc -aes-256-cbc -pass pass:YOUR-ENCRYPTION-PASSWORD -e > /subversion/svn_backups/repo_name-backup-$todaysdate.enc

#fyi to decrypt :
#openssl aes-256-cbc -d -pass pass:YOUR-ENCRYPTION-PASSWORD -in repo_name-backup.enc -out decrypted.dump

# Transfer the files to Amazon S3 Storage via HTTPS
/usr/local/bin/ruby /usr/local/bin/s3sync/s3sync.rb --ssl -v --delete -r /subversion/svn_backups S3_BACKUPS:svn/svnbackup >> /var/log/offsite-backup.log 2>&1

if [ "$?" -eq 1 ]
then
        echo -e "***SVN OFFSITE BACKUP JOB, THERE WERE ERRORS***" >> /var/log/offsite-backup.log 2>&1
        cat /var/log/offsite-backup.log | mail -s "SVN Offsite Backup Job failed" your@email.com
        exit 1
else
        echo -e "Script Completed Successfully!" >> /var/log/offsite-backup.log 2>&1
        cat /var/log/offsite-backup.log | mail -s "SVN Offsite Backup Job Completed" your@email.com
        exit 0
fi

Note how I have provided an example , commented out within the script, on how you can go about decrypting the encrypted SVN dump file. You can also modify this script to back up to any offsite location, obviously. Just remove the s3sync related entries and replace with rsync or your preferred transport method.

I hope this makes your life easier!