Author: kanakorn.h

  • วิธี Bypass หน้า Privacy Error เนื่องจาก NET::ERR_CERT_INVALID บน Google Chrome

    Google Chrome พยายามปกป้องเราจาก Website ที่อาจจะมีความเสี่ยง ใช่ ดี แต่ว่า บางทีก็จำเป็น แบบว่า เป็นเครื่องทดสอบไรงี้ ยังไม่ได้ติดตั้ง Certificate จริงจัง

    ถ้าเจอหน้านี้ … คลิก Advanced บางทีก็จะเจอปุ่ม Proceed to website ก็จะเข้าได้

    แต่ถ้าไม่มีปุ่มนั้นหล่ะ ?!?!

    วิธีการง่ายสุด คือ พิมพ์คำว่า

    thisisunsafe

    พิมพ์ไปเลย ไม่ต้องเคาะ Enter ก็จะเข้าเว็บไซต์ได้เลย

    *** ใช้ในกรณีเป็น เว็บไซต์ทดสอบของตัวเองเท่านั้นนะ ***

  • วิธีติดตั้ง HTTPS ด้วย Certificate ของ Let’s Encrypt แบบ wildcard สำหรับ Intranet ที่ไม่สามารถเข้าถึงได้จาก Internet

    ความเดิมตอนที่แล้ว

    ต่อจาก วิธีติดตั้ง HTTPS ด้วย Certificate ของ Let’s Encrypt ซึ่ง เครื่องที่จะขอใช้ Certificate นั้น ต้องสามารถ “เข้าถึงได้” จาก Internet เพราะ จะต้องสร้าง File ไปวางในตำแหน่งที่ Let’s Encrypt CA สามารถเข้ามาตรวจสอบได้ว่าเป็น ผู้ที่มีสิทธิ์ในการจัดการ Domain Name นั้นจริง

    ปัญหาคือ

    • ในองค์กร ถ้าจะให้มีเซิร์ฟเวอร์ ที่สามารถเข้าถึงได้ จาก Internet ต้องเปิด Firewall ขององค์กร ซึ่ง ซับซ้อน และ มีความเสี่ยง
    • แล้ว ถ้ามีเครื่องภายใต้โดเมนเดียวกันอีกหลายเครื่อง ต้องทำทุกเครื่อง ซึ่งไม่สะดวกเลย

    ต้องเข้าใจก่อน

    Let’s encrypt มีวิธีการตรวจสอบความเป็นเจ้าของ Domain Name หรือที่เรียกว่า “Challenge” หลายวิธี ได้แก่

    1. HTTP-01 Challenge
    2. DNS-01 Challenge
    3. TLS-SNI-01 Challenge
    4. TLS-ALPN-01 Challenge

    วิธีที่ง่าย และทำกันทั่วไป คือ HTTP-01 คือ เมื่อ certbot client รับคำสั่งขอ certificate สำหรับโดเมนที่ต้องการแล้ว จะต้องเอาไฟล์ ไปวางที่

    http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>

    ซึ่ง ชื่อต้องตรงกับที่กำหนด และในไฟล์ จะเป็น Token และ Thumbprint ของ account key

    (ในทางเทคนิค อาจจะให้มีสักเครื่องในองค์กรที่เปิดให้เข้าถึงจาก Internet ได้ ทำหน้าที่ร้องขอ Certificate ได้ แล้วทำ HTTP-01 Challenge ให้ผ่าน ก็จะสามารถขอ Certificate ให้กับ Subdomain อื่น ๆ ได้)

    แต่มีอีกวิธีหนึ่ง คือ DNS-01 Challenge ซึ่ง ไม่จำเป็นต้องมีเครื่องที่เข้าถึงได้จาก Internet (แต่สามารถ ออก Internet ได้) แต่อาศัยการสร้าง TXT Record ตามที่กำหนดแทน ประมาณนี้

    _acme-challenge.<YOUR_DOMAIN> 300 IN TXT "<TOKEN>"

    วิธีการมีดังนี้

    0. เราจะขอ wildcard certificate โดเมนของ abc.ijk.xyz.psu.ac.th โดยการติดตั้ง Let’s Encrypts certbot client บน Ubuntu 20.04 ซึ่งเครื่องนี้ สามารถต่อ Internet ได้ แต่ไม่สามารถเข้าถึงได้จาก Internet

    1.ติดตั้ง Let’s Encrypts certbot client (ยุ่งยากหน่อย แต่จะได้ certbot version 1.19)

    sudo apt update
    sudo snap install core; sudo snap refresh core
    sudo apt-get remove certbot
    sudo snap install --classic certbot
    sudo ln -s /snap/bin/certbot /usr/bin/certbot

    หรือสั้นๆ ทำอย่างนี้ก็ได้ (ไม่ต้องใช้ snap แต่จะได้ certbot version 0.40)

    sudo apt update
    sudo apt install certbot

    2. สั่ง certbot ให้ไปขอเฉพาะ certificate จากนั้นบอกว่าจะติดตั้งเอง (manual) และ ขอใช้ DNS-01 Challenge

    sudo certbot certonly --manual --preferred-challenges dns

    3. กรอกข้อมูล โดเมนเนม ที่ต้องการ ในที่นี้ จะขอใช้ wildcard คือ *.abc.ijk.xyz.psu.ac.th

    Saving debug log to /var/log/letsencrypt/letsencrypt.log
    Plugins selected: Authenticator manual, Installer None
    Please enter in your domain name(s) (comma and/or space separated) (Enter 'c' to cancel): *.abc.ijk.xyz.psu.ac.th
    Obtaining a new certificate
    Performing the following challenges:
    dns-01 challenge for *.abc.ijk.xyz.psu.ac.th

    NOTE: The IP of this machine will be publicly logged as having requested this
    certificate. If you're running certbot in manual mode on a machine that is not
    your server, please ensure you're okay with that.
    Are you OK with your IP being logged?

    (Y)es/(N)o: y

    Please deploy a DNS TXT record under the name
    _acme-challenge.abc.ijk.xyz.medicine.psu.ac.th with the following value:
    xjid...4u1znkFXut0Y...n3ck89gGaUl75K4fI
    Before continuing, verify the record is deployed.

    Press Enter to Continue

    **** ตรงนี้ อย่าเพิ่งเคาะ Enter เด็ดขาด จนกว่า DNS admin จะดำเนินการเสร็จ *****

    4. แจ้งผู้ดูแล DNS ขององค์กร ว่า ให้เพิ่ม TXT Record ส่งข้อความตามนี้ไปให้เค้าก็น่าจะได้

    Please deploy a DNS TXT record under the name
    _acme-challenge.abc.ijk.xyz.medicine.psu.ac.th with the following value:
    xjid...4u1znkFXut0Y...n3ck89gGaUl75K4fI

    หรือจะให้ดี แจ้งไปตรง ๆ ว่า จะเอาแบบนี้

    _acme-challenge.abc.ijk.xyz.medicine.psu.ac.th 300 IN TXT 
    "xjid...4u1znkFXut0Y...n3ck89gGaUl75K4fI"

    5. รอ รอ รอ จนกว่า DNS Admin บอกว่า เสร็จแล้ว (แนะนำว่า ควรทำในเวลาที่สามารถประสานงานกับ DNS Admin ได้ เพราะ ในขั้นตอนข้อ 3. นั้น จะเคาะผ่านไม่ได้ หากแจ้ง DNS admin ไปแล้ว แต่เค้ายังไม่ทันทำให้ แล้วเราดันไปเคาะ Enter ผ่านไปก่อน TOKEN ที่แจ้งไป ก็จะใช้ไม่ได้)

    ตรวจสอบด้วย Google Admin Toolbox ก็ได้ ว่าได้ TXT ตรงตามที่กำหนดมาแล้ว ที่

    https://toolbox.googleapps.com/apps/dig/#TXT/

    สิ่งที่ควรได้ น่าจะมีหน้าตาประมาณนี้

    เมื่อมั่นใจว่า TXT Record ตรงกันแล้ว ค่อยกลับไปที่หน้าต่างในข้อ 3. แล้วกด Enter

    Let’s Encrypt certbot client จะแจ้งไปยัง CA server ให้ตรวจสอบ ถ้าผ่านก็จะได้ข้อคาวมประมาณนี้

    Waiting for verification…
    Cleaning up challenges
    Subscribe to the EFF mailing list (email: kanakorn.h@g.psu.ac.th).
    IMPORTANT NOTES:
    Congratulations! Your certificate and chain have been saved at:
    /etc/letsencrypt/live/abc.ijk.xyz.psu.ac.th/fullchain.pem
    Your key file has been saved at:
    /etc/letsencrypt/live/abc.ijk.xyz.psu.ac.th/privkey.pem
    Your cert will expire on 2021-02-17. To obtain a new or tweaked
    version of this certificate in the future, simply run certbot
    again. To non-interactively renew all of your certificates, run
    "certbot renew"
    If you like Certbot, please consider supporting our work by:
    Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
    Donating to EFF: https://eff.org/donate-le

    ตรวจสอบ Certification ด้วยคำสั่ง

    sudo certbot certificates

    จะได้รายละเอียดว่า Certificate ที่ได้มา เป็นของ domain อะไรบ้าง

    Found the following certs:
    Certificate Name: abc.ijk.xyz.psu.ac.th
    Serial Number: 3808642657a...........bbeacd6df8bacc
    Domains: *.abc.ijk.xyz.psu.ac.th
    Expiry Date: 2021-02-17 02:59:43+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/abc.ijk.xyz.psu.ac.th/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/abc.ijk.xyz.psu.ac.th/privkey.pem

    สุดท้าย เอา fullchain.pem และ privkey.pem ไปไว้ในเครื่อง web server ที่จะต้องการใช้ https เป็น subdomain ของ abc.ijk.xyz.psu.ac.th

    จากนั้น ตั้งค่าใน Web Server เช่น Apache2 ให้ใช้ Certificate ประมาณนี้

    <VirtualHost *:443>
    # ...
       SSLEngine on
       SSLCertificateFile /etc/ssl/letsencrypt/fullchain.pem
       SSLCertificateKeyFile /etc/ssl/letsencrypt/privkey.pem
    </VirtualHost>

    แล้วก็ restart เป็นอันเรียบร้อย

    ตรวจสอบการใช้งานจริง โดยการใช้ https://server1.abc.ijk.xyz.psu.ac.th/ จะได้แบบนี้

    หวังว่าจะเป็นประโยชน์ครับ

  • [บันทึกกันลืม] วิธีติดตั้ง django-auth-ldap

    การจะใช้ ldap backend บน django นั้น จะต้องติดตั้ง django-auth-ldap ซึ่งจะใช้คำสั่ง

    pip install django-auth-ldap

    แต่ว่า อาจจะเจอ Error ประมาณนี้

    เหตุเพราะ ใน container ที่ใช้ อาจจะไม่มีเครื่องมือในการ build

    วิธีแก้ไขคือ (ใน docker python:3.8) ให้ติดตั้ง package

    apt-get install -y python-dev libldap2-dev libsasl2-dev libssl-dev python-ldap build-essential

    แล้วจึงค่อยติดตั้งผ่าน pip

    pip install django-auth-ldap

    ก็จะเรียบร้อยครับ

  • วิธีสร้าง reverse proxy ด้วย Apache2 docker ด้วย subpath ใน Domain Name เดียวกัน

    ในการเขียน web application บน docker (ผมชอบใช้ django / flask) เมื่อต้องการ deploy ไว้บนเครื่อง Production Server ซึ่งแต่ละ application ก็จะเปิด port ต่าง ๆ กัน เช่น 8080, 8081, 8082 … ว่ากันไป

    เช่น จะมี URL เป็น IP Address แล้วตามด้วย Port อย่างนี้

    192.168.x.y:8080
    192.168.x.y:8081
    192.168.x.y:8082

    การสร้าง Reverse Proxy ทำให้ผู้ใช้สามารถ เข้าถึง web application เหล่านี้ได้ง่ายขึ้น โดยใช้วิธีการ เช่น ใช้ Web Server อย่าง Apache, Nginx ทำงานร่วมกับ DNS Server เพื่อจด Domain Name มายัง IP Address เช่น

    app1 IN A 192.168.x.y
    app2 IN A 192.168.x.y
    app3 IN A 192.168.x.y

    แล้วที่ Web Server ค่อยมากำหนดว่า เมื่อ ServerName เป็น app1.xxx.psu.ac.th จะส่งไปยัง 192.168.x.y:8080 อะไรทำนองนี้

    ปัญหาคือ ถ้าต้องจดทะเบียน DNS ไปเรื่อย ๆ ก็คงไม่ไหว อีกทั้ง เมื่อต้องใช้ HTTPS ก็ต้องไปทำทีละ domain name อีกด้วย

    แล้วจะทำอย่างไร ให้สามารถรวบ web application ที่อยู่บน production server ตัวเดียวกันนี้ ให้อยู่ใน domain name เดียวกัน แต่ แยกกันด้วย URL Path ด้านหลังแทน เช่น

    xxx.psu.ac.th/app1/
    xxx.psu.ac.th/app2/
    xxx.psu.ac.th/app3/

    และ ในเมื่อเราใช้ Docker บน Production Server อยู่แล้ว ก็ใช้ Web Server เป็น container ซะ

    วิธีการทำมีดังนี้

    1. สร้าง apache2 container ให้ชื่อว่า httpd รับการเชื่อมต่อที่ port 80 ด้วยคำสั่ง
    docker run -dit --name httpd -p 80:80 httpd:2.4

    2. ตอนนี้ เราจะได้ container ชื่อ httpd มาแล้ว ต่อไป copy ไฟล์ httpd.conf ออกมา ด้วยคำสั่ง

    docker cp httpd:/usr/local/apache2/conf/httpd.conf .

    3. ตอนนี้ เราจะได้ไฟล์ httpd.conf ออกมา ให้แก้ไขไฟล์ด้วย Text Editor ตามถนัด โดยการทำ Reverse Proxy ด้วย apache2 นั้น ต้องเปิดใช้ module ดังนี้ (ค้นหา และ uncomment) แล้ว Save

    LoadModule proxy_html_module modules/mod_proxy_html.so
    LoadModule proxy_module modules/mod_proxy.so
    LoadModule proxy_http_module modules/mod_proxy_http.so
    LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so

    4. เพิ่ม Directive “ProxyPass” และ “ProxyPassReverse” ตามด้วย URL Path ที่ต้องการของแต่ละ Application แล้ว URL ที่สร้างไว้ เสร็จแล้ว ก็ Save

    ProxyPass "/app1/"  "http://192.168.x.y:8080/"
    ProxyPassReverse "/app1/"  "http://192.168.x.y:8080/"
    ProxyPass "/app2/"  "http://192.168.x.y:8081/"
    ProxyPassReverse "/app2/"  "http://192.168.x.y:8081/"
    ProxyPass "/app3/"  "http://192.168.x.y:8082/"                
    ProxyPassReverse "/app3/"  "http://192.168.x.y:8082/"          

    5. ต่อไป ก็ copy file httpd.conf ที่แก้ไขแล้ว กลับเข้าไปใน container ด้วยคำสั่ง

    docker cp httpd.conf httpd:/usr/local/apache2/conf/httpd.conf

    6. สุดท้าย restart container ด้วยคำสั่ง

    docker restart httpd

    ก็ประมาณนี้ เวลาจะเพิ่ม หรือ เปลี่ยนอะไร ก็ ทำซ้ำขั้นตอน 4,5,6 ครับ

    หมายเหตุ: ในบาง web application อย่างเช่น Grafana ก็จะต้องไปแก้ไข root_url ให้ใช้ subpath ด้วย ในพวก django, flask ต้อง ALLOWED_HOST อีกเล็กน้อย ครับผม

  • [กันลืม] Elasticsearch API พื้นฐาน

    INDEX

    วิธีดูว่ามี index อะไรบ้าง

    GET /_cat

    Response

    =^.^=
    /_cat/allocation
    /_cat/shards
    /_cat/shards/{index}
    /_cat/master
    /_cat/nodes
    /_cat/tasks
    /_cat/indices
    /_cat/indices/{index}
    /_cat/segments
    /_cat/segments/{index}
    /_cat/count
    /_cat/count/{index}
    

    เมื่อทราบว่ามี index อะไรบ้าง ต้องการดูรายละเอียด ใส่ query string parameter (qrs) “v”

    GET /_cat/indices?v

    Response

    health status index                    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
    yellow open   logstash-2020.06.20      JeGeb67mSNKN3gCgC9AWCQ   1   1    4573283            0    808.7mb        808.7mb
    yellow open   logstash-2020.05.30      Bg8-hUUAS0m4VbGs0-1lIw   1   1    4124813            0    721.9mb        721.9mb
    yellow open   logstash-2020.05.31      qJX9VbaySG2ssBXMTwmapA   1   1    4333363            0    772.6mb        772.6mb
    yellow open   logstash-2020.06.21      9on104uKQnGllS5QZNyUKg   1   1    4712048            0    869.2mb        869.2mb
    yellow open   logstash-2020.06.22      lVfX4tHjSUeMF640wmIzLw   1   1    8441356            0      1.6gb          1.6gb
    yellow open   logstash-2020.06.01      P_9wbTd7Qo6YNme-NRvFLQ   1   1    7360129            0      1.3gb          1.3gb
    yellow open   logstash-2020.06.23      xvQRTdNHTt2wKfiSlvR87A   1   1    7631987            0      1.4gb          1.4gb
    yellow open   logstash-2020.06.02      H-kPw4FXQ-W1AphBfjtcMw   1   1    7344418            0      1.3gb          1.3gb
    

    ต้องการทราบว่า แต่ละ Fields มีความหมายอย่างไร ใช้ qrs ‘help’

    GET /_cat/indices?help

    Response

    health                           | h                              | current health status                                                                                            
    status                           | s                              | open/close status                                                                                                
    index                            | i,idx                          | index name                                                                                                       
    uuid                             | id,uuid                        | index uuid                                                                                                       
    pri                              | p,shards.primary,shardsPrimary | number of primary shards                                                                                         
    rep                              | r,shards.replica,shardsReplica | number of replica shards                                                                                         
    docs.count                       | dc,docsCount                   | available docs                                                                                                   
    docs.deleted                     | dd,docsDeleted                 | deleted docs                                                                                                     
    creation.date                    | cd                             | index creation date (millisecond value)                                                                          
    creation.date.string             | cds                            | index creation date (as string)                                                                                  
    store.size                       | ss,storeSize                   | store size of primaries & replicas 
    

    ต้องการแสดงเฉพาะบาง Fields ใช้ qrs ‘h=’

    GET /_cat/indices?h=idx,dc,ss&v

    Response

    idx                           dc      ss
    logstash-2020.06.20      4573283 808.7mb
    logstash-2020.05.30      4124813 721.9mb
    logstash-2020.05.31      4333363 772.6mb
    logstash-2020.06.21      4712048 869.2mb
    logstash-2020.06.22      8441356   1.6gb
    logstash-2020.06.23      7631987   1.4gb
    logstash-2020.06.01      7360129   1.3gb
    logstash-2020.06.02      7344418   1.3gb
    logstash-2020.06.24      7300718   1.4gb
    

    ต้องการดูขนาดจัดเก็บ ใช้ qrs ‘bytes=’

    GET /_cat/indices?h=idx,dc,ss&bytes=b&v

    Response

    idx                           dc         ss
    logstash-2020.05.30      4124813  756971768
    logstash-2020.06.20      4573283  848085505
    logstash-2020.05.31      4333363  810175019
    logstash-2020.06.21      4712048  911450929
    logstash-2020.06.22      8441356 1736003983
    logstash-2020.06.01      7360129 1455314526
    logstash-2020.06.23      7631987 1559554324
    logstash-2020.06.24      7300718 1506134380
    logstash-2020.06.02      7344418 1484297643
    logstash-2020.06.25      8409242 1747862513
    logstash-2020.06.03      4424701  860877705
    

    ต้องการเรียงลำดับ ใช้ qrs ‘s=’ และ สามารถกำกับ ‘:desc’, ‘:asc’

    GET /_cat/indices?h=idx,dc,ss&bytes=b&s=ss:desc&v

    ลบ INDEX

    DELETE /kx01

    DOCUMENTS

    Document เป็น JSON ที่มีรายละเอียดเกี่ยวกับการสร้างขึ้นมา เช่น _id, _version และ _source ซึ่ง source หรือ (stored fields)

    create / update if exist

    POST /kx01/_doc/1
    {
      "name": "kanakorn",
      "HN": "1746436"
    }
    

    โดยใน Index เดียวกับ เก็บ Document คนละ Schema กันก็ได้

    POST /kx01/_doc/2
    {
      "HR": "100",
      "RR": "88",
      "age": 10
    }
    

    Check if exist

    HEAD /kx01/_doc/1/

    Response

    200 - OK

    get a source (stored fields)

    GET /kx01/_doc/1/

    Response

    {
      "_index" : "kx01",
      "_type" : "_doc",
      "_id" : "1",
      "_version" : 1,
      "_seq_no" : 0,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "name" : "kanakorn",
        "HN" : "1234567"
      }
    }

    get only document value

    GET /kx01/_source/1/

    Response

    {
      "name" : "kanakorn",
      "HN" : "1234567"
    }

    อื่น ๆ

    • Source filtering
      • source_include
      • source_exclude
  • ขั้นตอนการขอใช้บริการ PSU Groupmail

    กรุณาดำเนินการกรอกเอกสาร ตามขั้นตอนต่อไปนี้ครับ
    Download เอกสาร

    http://group.psu.ac.th/docs/F_SD13.pdf

    วิธีกรอก

    https://sysadmin.psu.ac.th/2017/01/05/acrobat-reader-dc-free-but-sufficient/

    วิธีการใช้งานเบื้องต้น

    http://group.psu.ac.th/docs/MailingList-20090625.pdf


    แล้วส่งกลับมาทาง email ได้เลยครับ  ()

    PSU Groupmail ตั้งต้นจะเป็น @group.psu.ac.th นะครับหากต้องการให้แปลงเป็น @psu.ac.th ช่วยแจ้งด้วย
    ส่วนการส่ง Email ออกไปในนามหน่วยงาน ให้ทำตามนี้

    วิธีส่ง email ออกไปในนามหน่วยงาน ด้วย PSU Webmail
    https://sysadmin.psu.ac.th/2020/03/27/psu-webmail-sent-email-on-behalf-of/

    วิธีส่ง email ในนามหน่วยงาน ที่ออกจาก Gmail ให้เป็น @psu.ac.th หรือ @group.psu.ac.th

    https://sysadmin.psu.ac.th/2018/01/26/send-gmail-in-the-name-of-psu/

  • วิธีใช้งาน Google Form ให้เฉพาะผู้เกี่ยวข้องเท่านั้นที่จะตอบแบบฟอร์มได้ โดยต้องไม่บังคับ Login ด้วย Google Account ด้วย

    Google Form เป็นเครื่องมือสร้างแบบสำรวจยอดฮิต สร้างง่าย ใช้ง่าย เหมาะสำหรับงานที่เปิดให้ใครก็ได้ สามารถตอบแบบสอบถาม

    แต่ ถ้าต้องการให้เฉพาะคนในองค์กรซึ่งใช้ G Suite (เช่น กรณีของ มหาวิทยาลัยสงขลานครินทร์ ใช้ G Suite for Education โดเมน psu.ac.th เป็นต้น) ตอบแบบสอบถามเท่านั้น ก็พอจะทำได้ แต่ก็จะเจอปัญหาคือ ผู้ใช้ในองค์กรอาจจะ Login ด้วย Web Browser ซึ่ง Sign-In ด้วย Gmail ส่วนตัว ก็จะยุ่งยากหน่อย ต้องสลับ Account เป็นต้น แล้วยิ่งบางคน ใช้ LINE เพื่อ Scan QR Code แล้วก็ไปใช้ In-App Browser ซึ่งก็ไม่รู้ว่า Sign-In ด้วย Account ไหน ยิ่งไปกว่านั้น ถ้าเป็นการไปใช้งานนอกสถานที่ที่ใช้ประจำ Google ก็มักจะ Challenge โดยการ ให้ระบุ เบอร์โทรศัพท์มือถือ หรือ Email ที่ใช้ Recovery สร้างความวุ่นวายได้เป็นอย่างมาก

    แล้ว ก็มี Requirement “ง่าย ๆ” มาให้คิด

    • ต้องการออกแบบระบบ ลงคะแนน ให้คณะกรรมการ ซึ่งอยู่ทั้งในและนอกองค์กร
    • ท่าน ๆ เหล่านี้ ล้วน … ทรงคุณวุฒิ และ วัยวุฒิ
    • ใช้อุปกรณ์หลากหลาย
    • ส่งแบบฟอร์มไปให้กรอก โดยผ่าน Email ท่าน ๆ ซึ่งเป็น @yahoo.com, @hotmail.com, @gmail.com, @xxx.edu, @xxxxxxxxx
    • บางท่าน ไม่มี Email แต่มี LINE เท่านั้น
    • การลงคะแนน ต้องมั่นใจว่า กรรมการแต่ละท่าน เป็นผู้ลงคะแนนจริง ๆ
    • ท่าน ๆ ลงคะแนนได้ 1 เสียงเท่านั้น
    • เปลี่ยนใจได้ด้วย คือ ตอนแรกจะลงคะแนนอย่างนึง แล้วก็ Submit ไปแล้ว แต่ก็คิดว่า เอ๊ะ เปลี่ยนใจแระ (ในกรอบเวลา)
    • ไม่สามารถทราบได้ว่าใครเป็นผู้ลงคะแนนได้ (โดยง่าย)

    แนวทางการแก้ปัญหา

    Google Form มี Feature นึง ที่บางคนไม่เคยใช้ นั่นคือ “Get pre-filled link”

    Pre-filled Link กล่าวคือ ทำการกรอกข้อมูลบางอย่างใน Google Form แล้วส่งให้ผู้ใช้ เช่น เรารู้อยู่แล้วว่า จะส่งแบบฟอร์มนี้ ไปทาง Email ของกรรมการท่านนี้ ก็แทนที่ต้องให้ท่าน กรอกชื่อตัวเอง เราก็กรอกไปให้ท่านเลย อะไรทำนองนั้น

    Idea ของเราคือ จะสร้าง “Question” ชื่อ Token ขึ้นมา (จะตั้งว่าอะไรก็ได้นะ)

    แล้วคลิกที่ “Get Pre-filled Link”

    จะได้แบบฟอร์มพร้อมกรอกอย่างนี้ เราก็จะ Mark ตำแหน่งที่จะแทนค่า Token ด้วยการใส่คำอะไรก็ได้ แต่ในที่นี้ จะใส่เป็นคำว่า “token” ตัวพิมพ์เล็ก ไปใส่ แล้วคลิกปุ่ม Get Link จากนั้น คลิกปุ่ม COPY LINK

    Link ที่ได้ จะเป็นแบบนี้

    https://docs.google.com/forms/d/e/XXXXXXXXXX/viewform?usp=pp_url&entry.625502761=token

    จากนั้นแค่ค่า ข้อความ “token” ด้วยค่า Hash เช่น เอาชื่อ นามสกุล และ email address ของแต่ละคนมาเข้ารหัส MD5 (จะกล่าวถึงวิธีการได้มาในตอนท้าย) เช่น ได้เป็น

    b4771c1c4d65442b32d7029d13fb6e41

    ก็จะได้ URL ที่จะส่งให้ท่านกรรมการ อย่างนี้

    https://docs.google.com/forms/d/e/XXXXXXXXXX/viewform?usp=pp_url&entry.625502761=b4771c1c4d65442b32d7029d13fb6e41

    เมื่อผู้รับคลิก Link ก็จะได้หน้าตาฟอร์มแบบนี้

    คราวนี้ ในภาพใหญ่ ก็ต้องหาทางทำสร้าง Hash ของกรรมการแต่ละท่าน อันนี้เป็นตัวอย่าง

    ต่อไปนี้เป็นวิธีการหาค่า Hash (เขียนด้วย Google App Script)
    Source: https://www.answiz.com/questions/2594/hash-of-a-cell-text-in-google-spreadsheet

    function MD5 (input) {
      var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, input);
      var txtHash = '';
      for (i = 0; i < rawHash.length; i++) {
        var hashVal = rawHash[i];
        if (hashVal < 0) {
          hashVal += 256;
        }
        if (hashVal.toString(16).length == 1) {
          txtHash += '0';
        }
        txtHash += hashVal.toString(16);
      }
      return txtHash;
    }

    จากนั้นก็เขียน Script ส่ง Email / LINE แจ้งข้อความที่ต้องการ พร้อม Link ที่กรอกข้อมูล Hash ของแต่ละท่าน

    เป็นอันเรียบร้อย …

    เหมือนเดิม เพิ่มเติมคือ ความซับซ้อน

    คราวนี้ ท่าน ๆ ก็ลงคะแนนกันมา แต่ว่า

    • ช่อง Token นั้น บน Google Form มันแก้ไขได้ ไม่มีทาง Lock ทำให้บางท่าน เอ่อ เผลอ แก้ไข/ลบ/ทดลอง
    • บางท่าน ก็ ลงคะแนน Submit แล้ว อยากเปลี่ยนใจทำไงดี ?

    ดังนั้น ต้องหาทางทำให้

    • ถ้า Hash ไม่ตรงกับข้อมูลที่มี ไม่นับคะแนน
    • ถ้า Hash ตรง แต่ซ้ำ ๆ มา ให้นับคะแนนจาก Record ล่าสุด

    ก็มาร่าย Formula กันหน่อย

    หน้าตา Google Sheets ซึ่งเป็นผลการลงคะแนนที่ได้จาก Google Form

    ไปเขียนสูตรใน Column D โดย จะนำค่าจาก Column C ไปตรวจสอบกับ Hash ซึ่งขอยกตัวอย่างว่า อยู่ที่ Sheet2 ใน Column A โดยใช้สูตร

    =IF(ISNA(VLOOKUP(C2,Sheet2!A:A,1,false)),"Not Match","Match")

    จากภาพแสดงให้เห็นว่า แถว 5 นั้น ค่า Hash ไม่ตรง จึงขึ้นคำว่า “Not Match”

    ต่อไป ก็ Filter เฉพาะที่ Match แล้ว Sort ตาม Timestamp

    สูตรประมาณนี้

    =sort(filter(A2:D6,D2:D6="Match"),3,True)

    คราวนี้ เราจะรู้ต้องหาว่า อันไหนหล่ะ ที่เป็น Record ล่าสุดในแต่ละกลุ่ม

    ใช้สูตรประมาณนี้

    =if(C14=C15,"","1")

    กล่าวคือ พอเรียงลำดับแล้ว ก็หาว่า บรรทัดไหน เป็นขอบของกลุ่ม ว่างั้น (อธิบายยากจัง) ก็แสดงค่า 1

    สุดท้าย จะก็ได้ผลสรุป Record ที่ Hash ตรง และเป็นการ Submit ล่าสุดของแต่ละกลุ่ม

    สูตรประมาณนี้

    =filter(A14:E17,E14:E17="1")

    ก็เอาไปทำ Chart สรุป ได้แล้ว

    หวังว่าจะเป็นประโยชน์ครับ

  • Spam Alert – หลอกเป็นธนาคารส่งแจ้งเตือนเรื่องการลงชื่อเข้าใช้อุปกรณ์ใหม่

    มุขเดิม เปลี่ยนธนาคาร

    28 เมษายน 2563 08:18 พบอีเมล “หลอกลวง” ว่ามาจากธนาคาร ไทยพานิชย์ แจ้งเกี่ยวกับ อุปกรณ์ใหม่ลงชื่อเข้าใช้

    ลักษณะอีเมลเป็นดังภาพ หากผู้ใดได้รับ ลบทิ้งได้ทันที

    รายละเอียดวิธีการสังเกต อ่านได้จาก Spam Alert – หลอกเป็นธนาคารส่งแจ้งเตือนเรื่องการลงชื่อเข้าใช้อุปกรณ์ใหม่

    คราวนี้ ผมลองคลิกเข้าไปดู ว่าหน้าตา Phishing เป็นอย่างไร

    เหมือนหน้าตาของ SCB Easy เป๊ะ ใครหลงเชื่อ (โดนหลอกสำเร็จ) ก็อาจจะสูญเงินในบัญชีไปได้ เพราะ Hacker ได้ username/password ของธนาคารไปแล้ว

    แถม เดี๋ยวนี้ เข้ารหัส HTTPS มาด้วย

    ความไม่รู้ คือ ความเสี่ยง

    ขอให้โชคดี

  • [บันทึกกันลืม] แก้ปัญหา server certificate verification failed บน gitlabs

    ปัญหาคือ: จะ git push จาก Linux/Ubuntu มาเก็บใน gitlabs ขององค์กร

    git push -u origin master

    แต่ติดปัญหาว่า

    fatal: unable to access 'https://gitlab.xxx.xxx.xxxx/userid/repo.git/': server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none

    วิธีแก้ปัญหา (แบบรีบ ๆ)

    git config --global http.sslVerify false

    ก็จะใช้งานได้แล้ว

    *** เออ “ก็ง่าย ๆ” แต่ไม่มีบอกไว้ให้หาง่าย ๆ นิ ***