или «Не нравится интерфейс от Cisco — сделай свой»
Беспроводные контроллеры 2500/ 5500 используются для управления точками доступа Cisco Aironet с прошивкой LWAPP в пределах корпоративной сети для обеспечения общей политики безопасности, гостевого доступа и поддерживают как стандартных компьютерных клиентов (ноутбуки, компьютеры, смартфоны), так и специализированные устройства с беспроводным доступом — ручные сканеры для торговых залов, беспроводные камеры наблюдения и т.д.
Не так давно, мне была поставлена задача организовать возможность выдачи гостевого доступа в интернет с использованием Cisco WLC. Доступ должен был выдавать наш «ресепшн» — то есть интерфейс должен быть максимально удобен и прост для людей далеких от IT. Само создание гостевого доступа должно было быть лишь частью процесса вместе с проверкой документов и выдачи временного бейджика и должно занимать не более 10 секунд.
В Cisco WLC для аутентификации пользователей есть возможность подключения внешнего RADIUS сервера (это может быть Cisco ISE или Windows NPS — но в нашем случае эти варианты отпадали) или воспользоваться локальной базой данных самого контроллера. Единственное ограничение локальной базы — это максимальное количество записей в базе: 2048.
В нашем случае, этого было более чем достаточно, и мы решили использовать возможности самого WLC. Для создания гостевых учетных записей можно создать специальный административный акаунт с ограниченными правами Lobby Admin (как видно из названия — предназначенный для целей сходных с нашими).
Почему нас не устроил стандартный Lobby Admin
Создав такой акаунт, мы решили посмотреть процесс создания гостевого юзера средствами Lobby Ambassador (так называется этот «урезанный» режим)
Шаг 1. Надо залогиниться — тут все понятно, надо ввести имя и пароль. В принципе, «ресепшн» может залогиниться в начале дня и не закрывать страницу, так что на скорость создания это влиять не будет
Шаг 2. Надо кликнуть на кнопку New
Шаг 3. Заполняем форму — здесь надо указать имя пользователя, сгенерировать пароль, указать время действия и выбрать сеть (гостевую)
Тут уже стало понятно, что в 10 секунд здесь никак не уложиться:
- Сгенерированный пароль показывается только в Javascript alert-e (в самой форме — звездочка) — чтобы распечатать, его надо или записывать на бумажке или делать скриншот
- Lifetime надо вписывать а не выбирать из списка
- Сеть надо выбирать каждый раз — невозможно установить default вариант
В итоге, стандартный интерфейс наш ресепшн не устроил, оно и понятно, UI в Cisco Web based приложениях традиционно не фонтан…
Как мы решили проблему
Достаточно просто, быстренько сваяли скрипт на PHP, коим и хотим поделиться
Прошу особо в код не вглядываться и не критиковать, человек пишет на PHP второй раз в жизни (и третий раз вообще программирует) — поэтому и вопросы стиля и security скрипта на рассматривались :).
<?php
error_reporting(0);
function generatePassword ($length = 8)
{
// start with a blank password
$password = "";
// define possible characters - any character in this string can be
// picked for use in the password, so if you want to put vowels back in
// or add special characters such as exclamation marks, this is where
// you should do it
$possible = "2346789bcdfghjkmnpqrtvwxyzBCDFGHJKLMNPQRTVWXYZ";
// we refer to the length of $possible a few times, so let's grab it now
$maxlength = strlen($possible);
// check for length overflow and truncate if necessary
if ($length > $maxlength) {
$length = $maxlength;
}
// set up a counter for how many characters are in the password so far
$i = 0;
// add random characters to $password until $length is reached
while ($i < $length) {
// pick a random character from the possible ones
$char = substr($possible, mt_rand(0, $maxlength-1), 1);
// have we already used this character in $password?
if (!strstr($password, $char)) {
// no, so it's OK to add it onto the end of whatever we've already got...
$password .= $char;
// ... and increase the counter by one
$i++;
}
}
// done!
return $password;
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Guest WIFI Access - Add a user</title>
<script>
function randomPassword(length)
{
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
pass = "";
for(x=0;x<length;x++)
{
i = Math.floor(Math.random() * 62);
pass += chars.charAt(i);
}
return pass;
}
</script>
</head>
<body >
<div id="test-header" class="accordion_headings" >Guest WIFI Network </div><!--Heading of the accordion ( clicked to show n hide ) -->
<!--Prefix of heading (the DIV above this) and content (the DIV below this) to be same... eg. foo-header & foo-content-->
<div id="test2-content"><!--DIV which show/hide on click of header-->
<p><br />
<?
if ($_REQUEST["action"]=="send") {
$headers = "MIME-Version: 1.0n" ;
$headers .= "Content-Type: text/html; charset="iso-8859-1"n";
$headers .= "Sensitivity: Personaln";
$message= "<table width=466 border=0 cellpadding=0 cellspacing=0 bordercolor=#000000> <tr>
<th colspan=2> Office Guest WIFI Access</th>
</tr>
<tr>
<td width=128>Username</td>
<td width=332><strong> ".$_REQUEST[User]." </strong></td> </tr> <tr> <td>Password</td> <td><strong> ".$_REQUEST[Pass]." </strong></td> </tr> <tr>
<td>Life Time</td> <td><strong> ".$_REQUEST[life]." days <br /> <font size=-2> starting from ".$_REQUEST[Date]." </font></strong> </td>
</tr>
</table><br>
Network Name is A_GUEST<br>
<b>By using Office WIFI Guest network you agree to everything listed in our policy document</b>. <br> For any IT related issues call helpdesk";
$status = mail($_REQUEST["email"], "Access to Office WIFI guest network", $message,$headers);
echo "<b>Info sent by Email";
}
if ($_REQUEST["action"]=="submit") {
$adduser="ok";
if (strlen($_REQUEST["User"])<2) { $adduser=""; $_REQUEST["action"]=""; $userermsg.="<br><font color=red>Username too short</font>"; }
if (strlen($_REQUEST["Pass"])<2) { $adduser=""; $_REQUEST["action"]=""; $userermsg.="<br><font color=red>Password too short</font>"; }
if ($adduser=="ok") {
// Adding user
$userermsg="";
$post = http_build_query(array(
"buttonClicked" => "4",
"userpwd" => $_REQUEST["Pass"] ,
"pwdconfirm" => $_REQUEST["Pass"],
"lifetime_days" => $_REQUEST["life"],
"lifetime_hours" => "0",
"lifetime_mins" => "1",
"lifetime_secs" => "1",
"apply" => "apply", "description" => "Email:".$_REQUEST["email"]." - ".$_REQUEST["notes"],
"GuestWlanID" => "0",
"guest_roleselect_checkbox" => "0",
"err_flag" => "0",
"username" => $_REQUEST["User"]
));
$context = stream_context_create(array("http"=>array(
"method" => "POST",
"header" => "Content-Type: application/x-www-form-urlencodedrn" .
"Content-Length: ". strlen($post) . "rn",
"content" => $post,
)));
$page = file_get_contents("http://lobbyadmin:lobbypassword@10.24.32.61/screens/aaa/guestuser_create.html", true, $context);
$usererr = strpos($page, 'ERROR: User Name', true); // As of PHP 5.3.0
if (intval($usererr)>1) { $_REQUEST["action"]=""; $userermsg.="<br><font color=red>User already exists! Please choose another name</font>"; }
if ($userermsg=="") {
//User created - give options - printout send by email
?>
<script type="text/javascript">
var win=null;
function printIt(printThis)
{
win = window.open();
win.focus();
win.document.open();
win.document.write('<'+'html'+'><'+'head'+'><'+'style'+'>');
win.document.write('body, td { font-family: Verdana; font-size: 10pt;} table { margin: 1em; border-collapse: collapse; } td, th { padding: .3em; border: 1px #ccc solid; }');
win.document.write('<'+'/'+'style'+'><'+'/'+'head'+'><'+'body'+'>');
win.document.write(printThis);
win.document.write('By using Office WIFI Guest network you agree to everything listed in our policy document. <br> For any IT related issues call (+41)(022)(909) <b>5555</b> <'+'/'+'body'+'><'+'/'+'html'+'>');
win.document.close();
win.print();
win.close();
}
</script>
</p>
<p class="style2">User has been successfully created </p>
<table width="466" border="0" cellpadding="0" cellspacing="0" bordercolor="#000000">
<tr>
<th colspan="2"> Office Guest WIFI Access</th>
</tr>
<tr>
<td width="128">Username</td>
<td width="332"><strong>
<?=$_REQUEST["User"]?>
</strong></td>
</tr>
<tr>
<td>Password</td>
<td><strong>
<?=$_REQUEST["Pass"]?>
</strong></td>
</tr>
<tr>
<td>Life Time</td>
<td><strong>
<?=$_REQUEST["life"]?>
days <br />
<font size="-2">
starting from
<?=date("d/M/Y H:i:s")?>
</font></strong> </td>
</tr>
</table>
<br />
<a href="#" onclick="printIt(document.getElementById('printme').innerHTML); return false">
Print guest access leaflet </a> <?
if (strlen($_REQUEST["email"])>5 ) { ?> <br />
<form action="index.php?action=send" method="post" id=sendemail>
<input type=hidden name="Date" value="<?=date("d/M/Y H:i:s")?>"/>
<input type=hidden name="User" value="<?=$_REQUEST["User"]?>"/>
<input type=hidden value="<?=$_REQUEST["Pass"]?>" id="Pass" name="Pass" />
<input type=hidden name="life" id="life" size="5" value="<? if(intval($_REQUEST["life"]==0)) { echo 1; } else { echo $_REQUEST["life"]; } ?>" />
<input type=hidden name="email" id="email" value="<?=$_REQUEST["email"]?>" /><a href=# onclick="document.getElementById('sendemail').submit(); return false;" >Send guest access leaflet by email </a>
</form> <? } ?>
</p>
<p>
<style>
table { margin: 1em; border-collapse: collapse; }
td, th { padding: .3em; border: 1px #ccc solid; }
</style>
<div id="printme" style="display:none">
<table width="466" border="0" cellpadding="0" cellspacing="0" bordercolor="#000000">
<tr>
<th colspan="2"> Office Guest WIFI Access</th>
</tr>
<tr>
<td width="128">Username</td>
<td width="332"><strong>
<?=$_REQUEST["User"]?>
</strong></td>
</tr>
<tr>
<td>Password</td>
<td><strong>
<?=$_REQUEST["Pass"]?>
</strong></td>
</tr>
<tr>
<td>Life Time</td>
<td><strong>
<?=$_REQUEST["life"]?>
days <br />
<font size="-2" >
starting from
<?=date("d/M/Y H:i:s")?>
</font></strong></td>
</tr>
<tr>
<td>Network Name <br />
(SSID)</td>
<td><strong>A_GUEST</strong></td>
</tr>
<tr>
<td colspan="2"><div align="center">Welcome to WiFi</div></td>
</tr>
</table>
</div>
<?
}
}
}
if ($_REQUEST["action"]=="") {
if ($_REQUEST["Pass"]=="") { $_REQUEST["Pass"]=generatePassword(4); }
?>
<form action="index.php" method="post" enctype="multipart/form-data"><?=$userermsg?>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="right">USERNAME</td>
<td align="left"><input name=User value="<?=$_REQUEST["User"]?>"/>
<input name="action" type="hidden" id="action" value="submit" /></td>
</tr>
<tr>
<td align="right">PASSWORD</td>
<td align="left"><input value="<?=$_REQUEST["Pass"]?>" id=Pass name=Pass />
[<a href=# onclick="document.getElementById('Pass').value=randomPassword(4); return false;">regenerate</a>] </td>
</tr>
<tr>
<td align="right">LIFETIME*</td>
<td align="left">
<input name="life" id="life" size="5" value="<? if(intval($_REQUEST["life"]==0)) { echo 1; } else { echo $_REQUEST["life"]; } ?>" /> <a href=# style="text-decoration:none" class="links" onclick="document.getElementById('life').value=parseInt(document.getElementById('life').value)+1; return false;">[+]</a> <a style="text-decoration:none" href=# class="links" onclick="if (parseInt(document.getElementById('life').value)>1){document.getElementById('life').value=parseInt(document.getElementById('life').value)-1; }return false;">[-]</a> <a style="text-decoration:none" href=# class="links" onclick=" document.getElementById('life').value=29;return false;">[month]</a> <a style="text-decoration:none" href=# class="links" onclick=" document.getElementById('life').value=0;return false;">[0]</a> </span></td>
</tr>
<tr>
<td align="right"> </td>
<td align="left">* days</td>
</tr>
<tr>
<td align="right"> </td>
<td align="left"> <input type="submit" value=Add /> </td>
</tr>
<tr>
<td align="right" valign="top" > </td>
<td align="left"> </td>
</tr>
<tr>
<td align="right" valign="top" ><div align="center"></div></td>
<td align="right" valign="top" ><div align="left"><strong>Optional information</strong></div></td>
</tr>
<tr>
<td align="right" valign="top"> </td>
<td align="left"> </td>
</tr>
<tr>
<td align="right" valign="middle">Email</td>
<td align="left"><input name="email" id="email" value="<?=$_REQUEST["email"]?>" />
<br /></td>
</tr>
<tr>
<td align="right" valign="middle">Additional info</td>
<td align="left"><input name="notes" type="text" id="notes" value="<?=$_REQUEST["notes"]?>" size="2" /> </td>
</tr>
<tr>
<td align="right" valign="middle"> </td>
<td align="left"><a href=# onclick="document.getElementById('notes').value='staff member'; return false;">SM</a> | <a href=# onclick="document.getElementById('notes').value='natcom user'; return false;">NC</a>| <a href=# onclick="document.getElementById('notes').value='consultant'; return false;">cons</a>| <a href=# onclick="document.getElementById('notes').value='field office user'; return false;">FO</a> | <a href=# onclick="document.getElementById('notes').value='partner company'; return false;">partn</a> </td>
</tr>
</table>
<p align="right"> </p>
<p><br />
<br />
</p>
</form>
<? } ?>
</div>
</div>
<!--End of each accordion item-->
<!--Start of each accordion item-->
</div>
</body>
</html>
Принцип работы довольно простой, PHP генерирует все поля формы и посылает POST запрос на WLC
Доступ к скрипту нужно ограничить (например с помощью htpasswd). Мы это сделали с помощью mod_ntlm, что заодно избавило от необходимости вводить пароль — используется integrated authehtification
Также можно использовать curl и подключаться по https — в нашем случае это не критично, так как веб-сервер и WLC подключены между собой через изолированный management VLAN
Что получилось
Получился интерфейс, полностью устроивший персонал «ресепшена».
Вот что им надо сделать для создания гостевого акаунта:
1. Пройти по адресу (ярлык на рабочем столе)
2. Сразу открывается форма создания юзера (login не требуется, пароль прегенерирован)
3. Вписывается имя пользователя (здесь можно AJAX-ом проверять существует ли юзер с таким логином — мы пока это не реализовали)
4. В основном доступ выдается на день (что и выставлено default) — можно увеличить ссылочками + и — , или дать сразу на месяц или неделю
5. Засабмитить и распечатать
Больше 10 секунд это теперь не занимает!
Автор: EminH