Замена стандартного Lobby Admin Cisco Wireless LAN Controller 5500/2500

от автора

или «Не нравится интерфейс от 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.0\n" ;         $headers .= "Content-Type: text/html; charset=\"iso-8859-1\"\n";          $headers .= "Sensitivity: Personal\n";  $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-urlencoded\r\n" .                  "Content-Length: ". strlen($post) . "\r\n",        "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 секунд это теперь не занимает!

ссылка на оригинал статьи http://habrahabr.ru/post/178985/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *