.. _integrated-googleform-iprequest:

사용자 IP 신청 연동(With Google Form)
============================================

본 가이드는 Google Form(app script)을 활용하여 Genian NAC에 서비스중 하나인 IP신청시스템을 사용하는 방법을 안내합니다. 

개요
---------------

Google Form의 Apps script와 Genian NAC의 RestFul API를 활용하여 Genian NAC를 사용하는 사용자가 네트워크 자원을 사용하기 전
보다 편리하게 IP와 MAC을 신청/승인 받고 비인가/인가 사용자에 대한 감사 증적을 남길 수 있습니다. 

권장 버전
^^^^^^^^^^^^

.. csv-table::
     :header: "제품명 (구성요소)", "버전", "비고"
     :widths: 30 30 40

     "Genian NAC (정책서버)", "V5.0.55 이상", ""

연동의 목적
-------------------

Goole Form과 Genian NAC IP 신청 연동은 다음의 효과를 제공합니다. 

**사용자 친화적 IP 신청 프로세스**
 - 일정한 틀이 만들어져있는 기본 제공 IP 신청화면이 아닌 사용자측에서 개발한 UI를 사용하여 IP신청 시스템을 구축할 수 있습니다. 

**관리자 친화적 IP 신청 프로세스**
 - 관리자가 원하는 IP 신청/승인 프로세스를 만들 수 있으며 언제든지 프로세스를 변경 할 수 있습니다. 

IP신청 연동 시나리오
-------------------------------

1. 사용자가 Google Form 으로 신청 내용 작성
2. Google Sheets 에 신청 내용 등록
3. 신청한 내용을 바탕으로 Genian NAC에 IP 신청
4. 관리자가 Google Sheets에서 승인으로 값 변경
5. 관리자가 승인한 IP 신청서 승인

.. image:: /images/googleformiprequest1.png
   :width: 600px


사전 준비 사항
--------------------

- 외부와 통신이 가능한 Genian NAC 정책 서버
- 관리자의 API 키 
- 정책서버에 등록된 센서 장비 및 센서의 노드 아이디
- Google Form

1. Genian NAC

| Google Apps Script에서 NAC의 API를 호출하기 위해서는 네트워크 접근이 가능해야하기 때문에 
| 클라우드 기반의 NAC 환경이거나 NAC에 접근할 수 있도록 도메인 설정을 먼저 해야합니다. 여기서는 접근 편의성을 위해 Test용 Cloud NAC를 생성하여 진행합니다. 

- Cloud NAC 사이트 생성
 
 - genians(https://my.genians.co.kr/) 사이트에서 계정 생성 및 사이트 생성

- 센서 설정 및 API Key 발급
 
 - IP 신청을 하기 위해서는 관리하고 있는 네트워크에 NAC 센서를 연결하여 생성한 사이트에 등록해야합니다. NAC 센서 생성 및 설정은 :ref:`installing-network-sensor` 를 참고하세요.
  
  - API 연동을 위해서는 관리자의 API Key를 생성해야합니다.
 
    - 관리 > 사용자 > 관리자의 상세화면에서 API Key를 생성합니다.

- 센서 Node ID 확인
 
 - IP 신청을 할 때 사용하고자하는 IP대역을 선택하게 되는데 이때 센서를 선택하게됩니다. 선택하는 장비의 Key가 Sensor Node ID입니다. 
 - 시스템 > 센서설정 > 센서 상세화면의 Node ID를 확인합니다.

2. Google Form

- 사용자에게 IP 신청을 받을 수 있는 Google 설문지 폼을 설정합니다.
 
 - Google Drive 메뉴에서 Google 설문지 메뉴를 선택합니다. 

- IP 신청서 작성 시 필수 입력이 필요한 항목을 설정합니다.(※ 각 사이트마다 요구사항은 다를 수 있으며 환경에 맞게 구성합니다)
 
 - 본 문서에서는 기본적인 항목을 기반하여 설정했으며 설정내용은 아래와 같습니다. 

      .. code-block:: bash
        
        사용위치: 드롭다운
         - 사용하고 있는 센서를 추가합니다. 여러 항목중에 선택할 수 있도록 드롭다운을 사용하며 구분을 위해 IP 주소를 라벨로 지정했습니다.
        사용자 ID: 단답형 텍스트
         - 사용자의 ID를 입력합니다. 
        사용자 명: 단답형 텍스트
        신청자 ID: 단답형 텍스트
        신청자 명: 단답형 텍스트
        용도: 드롭다운
         - 유동, 고정 IP 사용인지 용도를 선택할 수 있습니다. 
        IP 주소: 단답형 텍스트
         - 고정 IP 용도인 경우 IP를 입력합니다. 
        MAC 주소: 단답형 텍스트
         - 고정 IP 용도인 경우 MAC 주소를 입력 받습니다. 
        이메일: 단답형 텍스트
         - 신청 결과를 받을 이메일 주소를 입력받습니다. 

      .. image:: /images/googleformiprequest2.png
         :width: 600px

      .. note:: 질문에 사용되는 항목명은 Apps script 에서 그대로 사용되는 값이므로 정확히 작성합니다. 

- Sheets의 응답탭으로 이동하여 결과에 대한 Sheets를 생성합니다.
 
 - 응답 > Sheets에서 보기 클릭

- Sheets 가장 마지막 열에서 컬럼 두개를 추가 합니다. 

 - 승인여부: 관리자가 승인 여부를 선택할 수 있도록 컬럼을 추가합니다.(드롭다운)
 - idx: IP 신청 이후 정책서버에 등록되는 신청서의 index 값을 업데이트 할 컬럼입니다.

3. Apps Script

- 설문이 작성되어 시트에 내용이 저장될 때마다 실행될 스크립트를 작성합니다.
- 실행할 스크립트 종류는 두종류 입니다.

 - 신청서 작성 시 신청서 등록 스크립트
 - 관리자 승인 시 신청서 승인 스크립트

- Sheets 메뉴 > 확장 프로그램 > Apps Script를 선택합니다.

 - 스크립트를 작성하기 전 NAC Swagger 페이지에서 사용할 API를 확인합니다. 
 - swagger는 Genian NAC에서 사용가능한 API를 문서화한 도구이며 {https://Domain/mc2/swagger/index.html} 에서 확인 가능합니다. 
 - 생성과 승인을 하기 위해 POST **/mc2/rest/applications/ips** (신청서 생성) 와 PUT **/mc2/rest/applications/ips/신청서idx** (신청서 상태 수정) API를 확인합니다.

    .. image:: /images/googleformiprequest3.png
        :width: 600px

- Google form이 작성될 때마다 실행될 스크립트를 작성합니다. 

 - IP 신청서 등록

    .. code-block:: bash

        function onFormSubmit(e) {
          const itemResponses = e.namedValues;

          // 관리자의 API Key를 생성한 뒤 api 호출 시 사용할 수 있도록 선언한다.
          const API_KEY = "관리자의 API KEY 값"
          // REST API 를 호출할 url
          const url = "https://[도메인]/mc2/rest/applications/ips";

            const applyResJson = applyIpApplication(url, API_KEY, itemResponses);
            if(applyResJson !== null) {
              const idx = applyResJson.ipApps[0].idx;
              const sheet = e.range.getSheet();
              const row = e.range.getRowIndex();
              sheet.getRange(row, 12).setValue(idx);
            }
          }

              /**
          * IP 신청서 신청 후 응답값을 json 객체로 반환
          */
          function applyIpApplication(_url, apiKey, itemResponses) {
            const url = _url + "?apiKey=" + apiKey;
            const payload = getApplicationPayload(itemResponses);
            // REST API 호출
            const options = {
              "method": "POST",
              "contentType": "application/json;charset=UTF-8",
              "headers": {
                "accept": "application/json;charset=UTF-8"
              },
            "payload": payload
            };
            const response = UrlFetchApp.fetch(url, options);
            let resJson = null;
            try {
              resJson = JSON.parse(response.getContentText());
            } catch(e) {
              resJson = null;
            }
            return resJson;
          }
          /**
          * IP 신청서 신청 시 입력된 값을 json string 으로 변환
          */
          function getApplicationPayload(itemResponses) {
            // 센서의 nid을 가져올 수 있도록 객체로 미리 선언
            const sensorDatas = {'172.29.132.0/24': '[센서 Node ID]'
                                };
            // 용도의 value 값 선언
            const purposeDatas = {'유동IP사용': 'USERIP_VARIABLE',
                                    '고정IP사용': 'USERIP_STATIC'};
            // 응답 데이터 가공
            const purposeType = itemResponses["용도"][0];
            const data = {};
            // 신규신청 : 1
            data["appType"] = 1;
            data["sensorNid"] = sensorDatas[itemResponses["사용위치"][0]];
            data["id"] = itemResponses["사용자 ID"][0];
            data["name"] = itemResponses["사용자 명"][0];
            data["applicantId"] = itemResponses["신청자 ID"][0];
            data["applicantName"] = itemResponses["신청자 명"][0];
            data["purposeCode"] = purposeDatas[itemResponses["용도"][0]];
            if(purposeType === '고정IP사용') {
            data["ipStr"] = itemResponses["IP 주소"][0];
            data["mac"] = itemResponses["MAC 주소"][0];
          }
            data["alarmEmail"] = itemResponses["이메일"][0];
            const payload = JSON.stringify([
              data
            ]);
          return payload;
          }

    - function 설명
    
     - onFormSubmit()
        - Google form이 작성된 후 실행되는 진입점이 되는 function
    
     - applyIpApplication()
        - 신청서 API를 호출하는 function. 작업이 완료되면 결과값을 json 형태로 리턴합니다.
    
     - getApplicationPayload()
        - 신청서 API 호출 시 form에 입력된 정보를 바탕으로 파라미터를 생성합니다.

    - 사전 정의한 정보

        .. code-block:: bash
        
              const sensorDatas = {'172.29.50.xx': '센서 Node ID'}; 

         // 용도의 value 값 선언
          const purposeDatas = {'유동IP사용': 'USERIP_VARIABLE', 
                                  '고정IP사용': 'USERIP_STATIC'};

        - IP 신청을 할 때 센서의 Node ID 값을 파라미터로 전송해야하는데 form에 Node ID를 노출하는것은 사용자가 인식하기 어려운 정보이기 때문에 해당 부분이 치환이 될 수 있도록 사전에 정의해놓습니다. 
    
 - IP 신청 승인

    .. code-block:: bash
       
      /**
      * 시트에서 승인 컬럼 값이 변경되면 호출
      * @param {e} 이벤트 객체
          */
        function onEdit(e) {
          const sheet = e.source.getActiveSheet();
          const targetColumn = 11; //  idx 컬럼, 0 시작 배열에서 가져오는 값이기 때문에 컬럼 인덱스 값 -1
          const editedRow = e.range.getRow();
          const val = e.range.getValue();
          // 수정된 셀의 ROW에 idx값이 있는 경우에만 처리
        if (val === '승인') {
          const record = sheet.getRange(editedRow, 1, 1, sheet.getLastColumn()).getValues()[0];
          const idx = record[targetColumn];
          if(idx !== '') {
          approveIpApplication(idx);
          Logger.log("Value of val: " + idx);
          }
      }
      }
      /**
      * IP 신청서 승인
      */
      function approveIpApplication(idx) {
      // 관리자의 API Key를 생성한 뒤 api 호출 시 사용할 수 있도록 선언한다.
      const API_KEY = "[관리자 API KEY값]"
      const apiUrl = "[도메인]/mc2/rest/applications/ips/" + idx + "?apiKey=" + API_KEY;
      const options = {
          "method": "PUT",
          "contentType": "application/x-www-form-urlencoded",
          "headers": {
          "accept": "application/json;charset=UTF-8"
          },
          "payload": "cmd=approve"
      };
          try {
          const response = UrlFetchApp.fetch(apiUrl, options);
      } catch (err) {
          Logger.log("Error: " + err);
      }
      }
         
    - function 설명
    
     - onEdit()
        - 시트의 컬럼값을 관리자가 수정했을때 호출되는 function
        - 관리자가 수정한 컬럼이 승인여부 컬럼이면서 승인으로 값을 변경할 경우 approveIpApplication() function을 호출한다.
    
     - approveIpApplication()
        - 선택한 레코드의 IP 신청서를 승인한다.

- 트리거로 등록
 
 - 트리거 메뉴로 이동합니다. 

 - 트리거 추가

    - IP 신청서 등록 트리거

        - 실행할 함수 선택: 스크립트에 작성한 onFormSubmit() 선택
        - 실행할 배포: Head
        - 이벤트 소스 선택: 스프레드시트에서
        - 이벤트 유형 선택: 양식 제출 시

        .. image:: /images/googleformiprequest4.png
            :width: 400px
 
    - IP 신청서 승인 트리거

        - 실행할 함수 선택: 스크립트에 작성한 onFormSubmit() 선택
        - 실행할 배포: Head
        - 이벤트 소스 선택: 스프레드시트에서
        - 이벤트 유형 선택: 수정 시

        .. image:: /images/googleformiprequest5.png
            :width: 400px

 - 설문 작성 및 실행 확인

    - 실행 확인

        - 설문을 작성 후 확인
        - 시트에서 승인 후 확인
        - Logger.log 로 확인할 내용을 작성해 놓으면 실행단계에서 확인이 가능합니다.

- NAC에서 IP 신청 결과 확인
    
    - 관리 > 신청 > IP 사용 신청서 > 결과조회 메뉴 이동
    - 승인된 신청서 확인

    .. image:: /images/googleformiprequest6.png
            :width: 600px