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

в 8:54, , рубрики: Cisco, cisco wlc, Сетевые технологии, метки: ,

или «Не нравится интерфейс от Cisco — сделай свой»

Беспроводные контроллеры 2500/ 5500 используются для управления точками доступа Cisco Aironet с прошивкой LWAPP в пределах корпоративной сети для обеспечения общей политики безопасности, гостевого доступа и поддерживают как стандартных компьютерных клиентов (ноутбуки, компьютеры, смартфоны), так и специализированные устройства с беспроводным доступом — ручные сканеры для торговых залов, беспроводные камеры наблюдения и т.д.

Не так давно, мне была поставлена задача организовать возможность выдачи гостевого доступа в интернет с использованием Cisco WLC. Доступ должен был выдавать наш «ресепшн» — то есть интерфейс должен быть максимально удобен и прост для людей далеких от IT. Само создание гостевого доступа должно было быть лишь частью процесса вместе с проверкой документов и выдачи временного бейджика и должно занимать не более 10 секунд.

В Cisco WLC для аутентификации пользователей есть возможность подключения внешнего RADIUS сервера (это может быть Cisco ISE или Windows NPS — но в нашем случае эти варианты отпадали) или воспользоваться локальной базой данных самого контроллера. Единственное ограничение локальной базы — это максимальное количество записей в базе: 2048.

В нашем случае, этого было более чем достаточно, и мы решили использовать возможности самого WLC. Для создания гостевых учетных записей можно создать специальный административный акаунт с ограниченными правами Lobby Admin (как видно из названия — предназначенный для целей сходных с нашими).

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

Почему нас не устроил стандартный Lobby Admin

Создав такой акаунт, мы решили посмотреть процесс создания гостевого юзера средствами Lobby Ambassador (так называется этот «урезанный» режим)

Шаг 1. Надо залогиниться — тут все понятно, надо ввести имя и пароль. В принципе, «ресепшн» может залогиниться в начале дня и не закрывать страницу, так что на скорость создания это влиять не будет
Шаг 2. Надо кликнуть на кнопку New
Замена стандартного Lobby Admin Cisco Wireless LAN Controller 5500/2500
Шаг 3. Заполняем форму — здесь надо указать имя пользователя, сгенерировать пароль, указать время действия и выбрать сеть (гостевую)
Замена стандартного Lobby Admin Cisco Wireless LAN Controller 5500/2500

Тут уже стало понятно, что в 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 не требуется, пароль прегенерирован)
Замена стандартного Lobby Admin Cisco Wireless LAN Controller 5500/2500
3. Вписывается имя пользователя (здесь можно AJAX-ом проверять существует ли юзер с таким логином — мы пока это не реализовали)
4. В основном доступ выдается на день (что и выставлено default) — можно увеличить ссылочками + и — , или дать сразу на месяц или неделю
Замена стандартного Lobby Admin Cisco Wireless LAN Controller 5500/2500
5. Засабмитить и распечатать

Больше 10 секунд это теперь не занимает!

Автор: EminH

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js