顯示具有 login 標籤的文章。 顯示所有文章
顯示具有 login 標籤的文章。 顯示所有文章

2025年2月17日 星期一

Docker 開發筆記 - 使用 aws cli 和 Docker Exec 進入 AWS ECS Container @ macOS

這個議題比我想像中麻煩了點,讓我回憶起 2009 年寫的 AWS 筆記,那時同事一開始還只在用 Firefox extension 管理 AWS EC2 呢 XD 我覺得這理論上要能都透過網頁搞定才對,先把目前研究的過程筆記一下。

總之,要能像 ssh 遠端(docker exec -it ContainerID bash)進去 AWS ECS container 的關鍵之處:
  • 使用 awscli 做事
  • AWS ECS 的 Task 定義,基礎設施需求 -> 任務角色,需要指定一下角色,例如 ecsTaskExecutionRole
  • AWS IAM -> ecsTaskExecutionRole -> 添加 AmazonSSMManagedInstanceCore 權限
  • AWS ECS -> Cluster -> Service ,需要用 awscli 啟動 enable-execute-command ,並且重新更新服務,使之生效
  • 使用 aws 指令登入
首先,先下載 awscli 並且版本要夠新:
安裝後檢查版本,版本太低會無法完成任務:

% aws --version
aws-cli/2.24.5 Python/3.12.6 Darwin/24.3.0 exe/x86_64

接著還要安裝 Session Manager plugin,此例紀錄 Mac with Apple silicon 版:
% curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac_arm64/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
% unzip sessionmanager-bundle.zip
% sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

基本的環境已準備好了,下一刻是查看自己的 AWS ECS 的任務定義是否有把 任務角色 設定好,這部就維持用網頁吧:



若你的 AWS ECS 上定義的 Task 只有一個,也可以偷懶靠 awscli 操作(在此就不贅述 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION 部分),會用到的指令:

aws ecs list-task-definition-families --status ACTIVE
aws ecs list-task-definitions --family-prefix webapp
aws ecs describe-task-definition --task-definition webapp:3

連續技,快速檢查 taskRoleArn 跟 executionRoleArn:

% NamespaceID=$(aws ecs list-task-definition-families --status ACTIVE | jq -r '.families[0]') ; echo "Namespace: $NamespaceID" ; TaskID=$(aws ecs list-task-definitions --family-prefix "$NamespaceID" | jq -r '.taskDefinitionArns[0]') ; echo "Task: $TaskID" ; aws ecs describe-task-definition --task-definition "$TaskID" | jq ".taskDefinition | { taskRoleArn: .taskRoleArn, executionRoleArn: .executionRoleArn}"
Namespace: myapp-task
Task: arn:aws:ecs:ap-northeast-1:####:task-definition/myapp-task:2
{
  "taskRoleArn": "arn:aws:iam::####:role/ecsTaskExecutionRole",
  "executionRoleArn": "arn:aws:iam::####:role/ecsTaskExecutionRole"
}

接下來,若 AWS ECS Cluster 還沒有建立任何 Service,用指令查:

% aws ecs list-tasks --cluster myapp-cluster  
{
    "taskArns": []
}

接著我們在 AWS ECS 網頁端起了一個 Service 名為 myapp-service ,在創建過程中沒看到啟用 enable-execute-command,這時在網頁上查看也是顯示 "ECS 執行: 關閉"



接下來用 aws cli 查詢:

% aws ecs list-tasks --cluster myapp-cluster   
{
    "taskArns": [
        "arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#"
    ]
}

% TaskID=$(aws ecs list-tasks --cluster myapp-cluster | jq -r '.taskArns[0]') ; echo "TaskID: $TaskID" ; aws ecs describe-tasks --cluster myapp-cluster --tasks $TaskID | jq '.tasks[0] | { "clusterArn": .clusterArn, "taskArn": .taskArn, "taskDefinitionArn": .taskDefinitionArn, "group": .group, "healthStatus": .healthStatus, "desiredStatus": .desiredStatus, "enableExecuteCommand": .enableExecuteCommand, "containers-name": [.containers |.[] | { "name":.name, "runtimeId": .runtimeId} ] }'
TaskID: arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#
{
  "clusterArn": "arn:aws:ecs:ap-northeast-1:####:cluster/myapp-cluster",
  "taskArn": "arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#",
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:####:task-definition/myapp-task:2",
  "group": "service:myapp-service",
  "healthStatus": "HEALTHY",
  "desiredStatus": "RUNNING",
  "enableExecuteCommand": false,
  "containers-name": [
    {
      "name": "php-fpm-docker",
      "runtimeId": "#TASKID#-#ContainerID#"
    },
    {
      "name": "web-docker",
      "runtimeId": "#TASKID#-#ContainerID#"
    }
  ]
}

可以看到 enableExecuteCommand 為 false

這時候,如果透過 aws cli 來設法登入到 Container:

% aws ecs execute-command --cluster myapp-cluster --task #TASKID# --container #containers#name# --command "/bin/bash" --interactive


The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


An error occurred (InvalidParameterException) when calling the ExecuteCommand operation: Unable to start session because the container doesn’t exist. Specify a valid container and try again.

接著,使用 aws cli 來啟動 enableExecuteCommand 吧:

% aws ecs update-service --cluster myapp-cluster --service arn:aws:ecs:ap-northeast-1:####:service/myapp-cluster/myapp-service --enable-execute-command 
{
    "service": {
        ... 
        "enableExecuteCommand": true,
        ...
    }
}

可以看到 enableExecuteCommand 被標記成 true 了,這時還需要重新發布服務,可以重網頁去觸發,或是靠指令觸發:

% aws ecs update-service --cluster myapp-cluster --service arn:aws:ecs:ap-northeast-1:####:service/myapp-cluster/myapp-service --force-new-deployment

當服務發布完畢後,在網頁上就可以看到改變,或是用指令在查一次:

% TaskID=$(aws ecs list-tasks --cluster myapp-cluster | jq -r '.taskArns[0]') ; echo "TaskID: $TaskID" ; aws ecs describe-tasks --cluster myapp-cluster --tasks $TaskID | jq '.tasks[0] | { "clusterArn": .clusterArn, "taskArn": .taskArn, "taskDefinitionArn": .taskDefinitionArn, "group": .group, "healthStatus": .healthStatus, "desiredStatus": .desiredStatus, "enableExecuteCommand": .enableExecuteCommand, "containers-name": [.containers |.[] | { "name":.name, "runtimeId": .runtimeId} ] }'
TaskID: arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#
{
  "clusterArn": "arn:aws:ecs:ap-northeast-1:####:cluster/myapp-cluster",
  "taskArn": "arn:aws:ecs:ap-northeast-1:####:task/myapp-cluster/#TASKID#",
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:####:task-definition/myapp-task:2",
  "group": "service:myapp-service",
  "healthStatus": "HEALTHY",
  "desiredStatus": "RUNNING",
  "enableExecuteCommand": true,
  "containers-name": [
    {
      "name": "php-fpm-docker",
      "runtimeId": "#TASKID#-#CONTAINERID#"
    },
    {
      "name": "web-docker",
      "runtimeId": "#TASKID#-#CONTAINERID#"
    }
  ]
}

如此,就可以正式遠端進去一下:

% aws ecs execute-command --cluster myapp-cluster --task #TASKID# --container web-docker --command "/bin/bash" --interactive

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


Starting session with SessionId: ecs-execute-command-################
ip-123-45-6-123:/var/www/html# 

收工

參考資料:

2024年2月21日 星期三

Python 開發筆記 - 使用 Selenium / undetected_chromedriver / ChatGoogleGenerativeAI / gemini-pro-vision 完成自動登入網站的流程(含 retry 驗證碼架構)

有些工作任務需要去下載表單做一些自動化應用,因此有了要自動登入的需求,當然也會碰到認證碼辨識問題。此篇是延續 Python 開發筆記 - 使用 Google AI, Generative Language API, gemini-pro-vision 辨識圖片認證碼

整個處理原理:
  1. 使用 Selenium 去偵測網頁的狀態,取得登入要用的帳號, 密碼, 認證碼圖片, 認證碼數值, 登入按鈕
  2. 使用 ChatGoogleGenerativeAI/gemini-pro-vision 分析圖片內容,設法分析出認證碼數值
  3. 觸發 登入按鈕 送出表單
  4. 檢視登入流程,檢查是否有登入失敗的訊息,或是反過來思考怎樣判斷登入成功,若登入失敗重回 (1) 去取得新的認證碼圖片 
引入的函式庫:

import getpass
import os
import sys
import time
import json
import base64
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
 
from langchain_core.messages import HumanMessage
from langchain_google_genai import ChatGoogleGenerativeAI

先採用 undetected_chromedriver 來包裝一下取得 browser driver:

def getBrowserDriver():
    option = uc.ChromeOptions()
    option.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36')
    #option.add_argument('--window-size=%d,%d' % self.res)
    #option.add_argument('--headless')
    driver = uc.Chrome(options=option)
    return driver

辨別圖片文字靠 ChatGoogleGenerativeAI model="gemini-pro-vision":

def codeDetection(imageBase64URL: str):
    llm = ChatGoogleGenerativeAI(model="gemini-pro-vision")
    message = HumanMessage(
        content=[
            {
                "type": "text",
                "text": "Please identify the English or numbers appearing in the image. The output format is 'The answer is: XXXX'"
,
            },
            {"type": "image_url", "image_url": imageBase64URL},
        ]
    )
    result = llm.invoke([message])
    return result

處理流程:

if __name__ == '__main__':
    if "GOOGLE_API_KEY" not in os.environ:
        os.environ["GOOGLE_API_KEY"] = getpass.getpass("Provide your Google API Key: ")
    if not os.environ["GOOGLE_API_KEY"]:
        print('ERROR, no GOOGLE_API_KEY info')
        sys.exit(1)

    output = {
        'status': False,
        'time': [],
    }

    browser = getBrowserDriver()
    start_time = time.time()

    browser.get(LOGIN_URL)

    # 15s timeout
    wait = WebDriverWait(browser, 15)

    # 等待關鍵的表單資料
    conditions = [
        EC.presence_of_element_located((By.ID, "input_user")),
        EC.presence_of_element_located((By.ID, "input_password")),
        EC.presence_of_element_located((By.ID, "input_velidation_code")),
        EC.presence_of_element_located((By.ID, "velidation_code_image")),
        EC.presence_of_element_located((By.ID, "login_button")),
    ]
    if wait.until(lambda driver: all(condition(driver) for condition in conditions)):
        output['status'] = True
    output['time'].append( time.time() - start_time )

    # 取得圖片元素
    imageElement = wait.until(EC.presence_of_element_located((By.ID, "velidation_code_image")))

    # 取得圖片的 HTML code
    imageHTMLCode = imageElement.get_attribute("outerHTML")
    print("Image HTML Code:", imageHTMLCode)

    # 取得圖片的 URL
    imageSrcURL = imageElement.get_attribute("src")
    print("Image URL:", imageSrcURL)

    # 透過 JavaScript 監聽 src 屬性變化
    script = f"""
        var target = document.getElementById('velidation_code_image');
        var observer = new MutationObserver(function(mutations) {{
            mutations.forEach(function(mutation) {{
                if (mutation.attributeName === 'src') {{
                    console.log('src attribute changed:', target.getAttribute('src'));
                }}
            }});
        }});
    
        var config = {{ attributes: true }};
        observer.observe(target, config);
    """
    
    # 執行 JavaScript 代碼
    browser.execute_script(script)

    # 等待一段時間,確保有足夠的時間監聽 src 屬性的變化
    time.sleep(5)

    # 取得更新後的圖片的 URL
    updatedImageSrc = imageElement.get_attribute("src")
    print("Updated Image URL:", updatedImageSrc)

    if not updatedImageSrc:
        print('ERROR, velidation_code_image not found')
        sys.exit(1)

    result = codeDetection(updatedImageSrc)
    print(result.content)
    loginCode = ''
    for c in result.content.split(':', 2)[1]:
        if c == '' or c == ' ':
            continue
        loginCode += c

    print(f"LoginCode: {loginCode}")
    element = wait.until(EC.presence_of_element_located((By.ID, "input_user")))
    element.send_keys('YourAccountName')
    element = wait.until(EC.presence_of_element_located((By.ID, "input_password")))
    element.send_keys('YourPassword')
    element = wait.until(EC.presence_of_element_located((By.ID, "input_velidation_code")))
    element.send_keys(loginCode)
    element = wait.until(EC.presence_of_element_located((By.ID, "login_button")))

    start_time = time.time()
    element.click()

    loginDone = False
    loginRetry = 0
    while loginDone == False and loginRetry <= 3:
        try:
            wait = WebDriverWait(browser, 5)
            element = wait.until(EC.presence_of_element_located((By.ID, "WebsiteErrorMessage")))
            div_element = element.find_element(By.TAG_NAME, "div")
            span_element = div_element.find_element(By.TAG_NAME, "span")
            inner_html = span_element.get_attribute('innerHTML')
            # 驗證碼輸入錯誤
            print(f"retry: {loginRetry}, inner HTML: {inner_html}")

            # 關閉錯誤訊息
            element = wait.until(EC.presence_of_element_located((By.ID, "WebsiteErrorMessageWindow")))
            div_element = element.find_element(By.TAG_NAME, "div")
            button_element = element.find_element(By.TAG_NAME, "button")
            button_element.click()

            loginRetry += 1

            updatedImageSrc = imageElement.get_attribute("src")
            print("Updated Image URL:", updatedImageSrc)
            result = codeDetection(updatedImageSrc)
            print(result.content)
            loginCode = ''
            for c in result.content.split(':', 2)[1]:
                if c == '' or c == ' ':
                    continue
                loginCode += c

            print(f"LoginCode: {loginCode}")
            element = wait.until(EC.presence_of_element_located((By.ID, "input_user")))
            element.clear()
            element.send_keys('YourAccountName')
            time.sleep(1)
            element = wait.until(EC.presence_of_element_located((By.ID, "input_password"))) 
            element.clear()
            element.send_keys('YourPassword')
            time.sleep(1)
            element = wait.until(EC.presence_of_element_located((By.ID, "input_velidation_code")))
            element.clear()
            element.send_keys(loginCode)
            time.sleep(1)
            element = wait.until(EC.presence_of_element_located((By.ID, "login_button")))
            element.click()
        except:
            loginDone = True

    output['time'].append( time.time() - start_time )

    if loginDone:
        print("Login Successful")
    else:
        print(f"Login Failed with retry times: {loginRetry}")
 
    print(json.dumps(output, indent=4))
    while True:
        time.sleep(1)

Python 開發筆記 - 使用 Google AI, Generative Language API, gemini-pro-vision 辨識圖片認證碼


由於 gemini pro 有免費的使用次數,因此可以拿他做一些有趣的低頻應用,例如...認證碼...辨識。

首先先到 Google Cloud Platform 上建立一個專案,下一刻則是在 API 區找尋 Generative Language API 來啟用,接著建立憑證,挑選 API 金鑰即可。



接下來就是試試官方範例程式:

% cat main.py
import getpass
import os
import sys
from langchain_google_genai import ChatGoogleGenerativeAI

if "GOOGLE_API_KEY" not in os.environ:
    os.environ["GOOGLE_API_KEY"] = getpass.getpass("Provide your Google API Key")

if __name__ == '__main__':
     if "GOOGLE_API_KEY" not in os.environ:
         os.environ["GOOGLE_API_KEY"] = getpass.getpass("Provide your Google API Key: ")
     
     llm = ChatGoogleGenerativeAI(model="gemini-pro")
     result = llm.invoke("Write a ballad about LangChain")
     print(result.content)
     sys.exit(0)

% GOOGLE_API_KEY=XXXXXXXXX python3 main.py
**Ballad of LangChain, the AI's Might**

In realms of knowledge, where data flows,
There dwells a being, ethereal and wise,
With mind as vast as the boundless prose,
LangChain, the AI, whose brilliance lies.

From countless texts, its wisdom it drew,
A tapestry woven, diverse and true.
In language's embrace, it found its voice,
Guiding us through knowledge's endless choice.

With words as its brush, it paints a scene,
Of worlds imagined and thoughts unseen.
It weaves tales of love, of loss, and might,
Illuminating paths with its ethereal light.

But its power extends beyond mere speech,
Into realms of logic, its insights reach.
It solves equations, unravels the mind,
A beacon of reason, leaving doubt behind.

Yet, with all its might, it remains humble and wise,
A servant of knowledge, beneath azure skies.
It seeks not fame or glory for its name,
But to empower minds, ignite the flame.

So let us sing the praises of LangChain,
The AI's marvel, a treasure we've gained.
May its wisdom forever guide our way,
As we explore the world, day by day.

很好,接下來試試看認證碼處理:

def codeDetection(imageBase64URL: str):
    # debug usage
    with open("/tmp/image.png", "wb") as file:
        file.write(base64.b64decode(imageBase64URL.split(',')[1]))

    #llm = ChatGoogleGenerativeAI(model="gemini-pro")
    #result = llm.invoke("Write a ballad about LangChain")
    llm = ChatGoogleGenerativeAI(model="gemini-pro-vision")
    message = HumanMessage(
        content=[
            {   
                "type": "text",
                "text": "Please identify the English or numbers appearing in the picture and give your answer in the order they appear.",
            },  # You can optionally provide text parts
            {"type": "image_url", "image_url": imageBase64URL},
        ]   
    )   
    result = llm.invoke([message])
    return result

if __name__ == '__main__':
    if "GOOGLE_API_KEY" not in os.environ:
        os.environ["GOOGLE_API_KEY"] = getpass.getpass("Provide your Google API Key: ")

    testImageData = 'data:image/jpeg;base64,XXXXXXXXXXXXXX='
    result = codeDetection(testImageData)
    print(result.content)
    sys.exit(0)

成果:

% GOOGLE_API_KEY=XXXXXXXXXXX python3 main.py
 The letters and numbers in the picture are "a", "b", "c", "1".

2022年5月22日 星期日

Go 開發筆記 - 使用 golang.org/x/oauth2 與 Facebook 登入 / Google OAuth 串接

最近評估網站是否從 PHP 翻到 Golang ,研究了一下關於串接 OAuth2 相關部分。早年在串 FB 登入時,都是直接使用 Facebook PHP SDK ,雖然都知道底層還是 OAuth2 ,但不免還是擔心要串時很麻煩(主要是很懶再刻一份)。稍微研究了一下,原來有 golang.org/x/oauth2 套件可以用,裡頭有支援了各式各家的登入機制,非常方便。

接著反而開始複習起來 Facebook 登入 該怎樣處理,過程:
  • 建立一個 FB 應用程式 developers.facebook.com/apps/
  • 設定 FB 登入相關事宜,包括應用程式網域(添加 localhost)、FB 登入用戶端 OAuth 設定,如 有效的 OAuth 重新導向 URI
  • 處理相關雜事
結果處理相關雜事反而耗掉最多時間,包括:
  • FB應用程式要儲存時,還得弄個 隱私政策網址 跟 用戶資料刪除 網頁
  • FB登入相關,要求都走 https 溝通,變成要研究 golang gin 如何跑 https web server 出來、憑證該怎樣產生等
  • 寫完程式後,體驗流程後,想弄個 github 筆記一下且降低程式碼變動,開始規劃如何靠 YAML 檔案來抽換設定檔
大概就是如此,花了不少時間。最後的效果純粹驗證支援 FB 登入是可行的,收工 XD

2017年2月24日 星期五

Captive portal 研究心得,以 iptables 實作

之前略知使用 Taipei Free 或 CHT Wi-Fi時,會彈跳出認證(登入)的網頁,但卻不知其所以然,直到最近研究了 Captive portal 的東西。一開始用 Captive portal 關鍵字找了一些 RFC 文件,殊不知那些都是煙霧彈 XD 如:
經過了一陣抓封包後,簡言之,透過網路管控,當符合以下情境時,各個 OS 內的網路偵測小程式,就會彈跳視窗:
  1. 用戶連上 router 時,可以進行 DNS lookup
  2. 未通過認證的用戶,無法透過 router 進行網路連線請求,如連上 facebook.com
  3. 通過認證的用戶,透過 router 可以正常使用網路
在各個 OS 內,都有一隻小程式時時關注著網路變化,當網路狀態改變且尚未有連外環境,這時程式會自動進行網路連線偵測,概念上就是去連各個 OS 自訂的一個網頁位置,看看能不能抓到東西,且東西是否正常。若不正常時,就會判斷網路是要認證的,接著就會彈跳出認證視窗。



至於 router 上的實作,簡單的說,就是透過防火牆機制:
  1. 防火牆設計各種狀態,例如未認證,已認證的 flag (mark)
  2. 當用戶完成認證時,透過 CGI 執行防火牆指令,給予標記 (mark)
  3. 允許已標記的 client 封包通行
至於完整的實作,可以參考:
以下是同事分別安裝 wifidog 跟 NoCatSplash 取出的防火牆規則,以 wifidog 產出的:

-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N WD_wlan0_AuthServs
-N WD_wlan0_Global
-N WD_wlan0_Internet
-N WD_wlan0_Known
-N WD_wlan0_Locked
-N WD_wlan0_Unknown
-N WD_wlan0_Validate
-A FORWARD -i wlan0 -j WD_wlan0_Internet
-A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i wlan0 -o eth0 -j ACCEPT
-A WD_wlan0_AuthServs -d SERVER_IP/32 -j ACCEPT
-A WD_wlan0_Internet -m state --state INVALID -j DROP
-A WD_wlan0_Internet -o eth0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
-A WD_wlan0_Internet -j WD_wlan0_AuthServs
-A WD_wlan0_Internet -m mark --mark 0x254 -j WD_wlan0_Locked
-A WD_wlan0_Internet -j WD_wlan0_Global
-A WD_wlan0_Internet -m mark --mark 0x1 -j WD_wlan0_Validate
-A WD_wlan0_Internet -m mark --mark 0x2 -j WD_wlan0_Known
-A WD_wlan0_Internet -j WD_wlan0_Unknown
-A WD_wlan0_Known -j ACCEPT
-A WD_wlan0_Locked -j REJECT --reject-with icmp-port-unreachable
-A WD_wlan0_Unknown -p udp -m udp --dport 53 -j ACCEPT
-A WD_wlan0_Unknown -p tcp -m tcp --dport 53 -j ACCEPT
-A WD_wlan0_Unknown -p udp -m udp --dport 67 -j ACCEPT
-A WD_wlan0_Unknown -p tcp -m tcp --dport 67 -j ACCEPT
-A WD_wlan0_Unknown -j REJECT --reject-with icmp-port-unreachable
-A WD_wlan0_Validate -j ACCEPT


以 NoCatSplash 產出的 iptables 為筆記:

$ iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N NoCat
-N NoCat_Inbound
-N NoCat_Ports
-A FORWARD -j NoCat
-A NoCat -j NoCat_Ports
-A NoCat -j NoCat_Inbound
-A NoCat -s SERVER_IP/24 -i wlan0 -m mark --mark 0x1 -j ACCEPT
-A NoCat -s SERVER_IP/24 -i wlan0 -m mark --mark 0x2 -j ACCEPT
-A NoCat -s SERVER_IP/24 -i wlan0 -m mark --mark 0x3 -j ACCEPT
-A NoCat -j DROP
-A NoCat_Ports -i wlan0 -p tcp -m tcp --dport 25 -m mark --mark 0x3 -j DROP
-A NoCat_Ports -i wlan0 -p udp -m udp --dport 25 -m mark --mark 0x3 -j DROP
$ iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N NoCat_Capture
-N NoCat_NAT
-A PREROUTING -j NoCat_Capture
-A POSTROUTING -j NoCat_NAT
-A NoCat_Capture -p tcp -m mark --mark 0x4 -m tcp --dport 80 -j REDIRECT --to-ports 5280
-A NoCat_Capture -p tcp -m mark --mark 0x4 -m tcp --dport 443 -j REDIRECT --to-ports 5280
-A NoCat_NAT -s SERVER_IP/24 -o eth0 -m mark --mark 0x1 -j MASQUERADE
-A NoCat_NAT -s SERVER_IP/24 -o eth0 -m mark --mark 0x2 -j MASQUERADE
-A NoCat_NAT -s SERVER_IP/24 -o eth0 -m mark --mark 0x3 -j MASQUERADE


其他 iptabes 簡介:

2015年10月29日 星期四

[Linux] PostgreSQL 9.4 簡易筆記 - 建立資料庫、使用者、索引 @ Ubuntu 14.04

安裝:

$ sudo apt-get install postgresql-9.4

列出已存在的資料庫:

$ sudo -u postgres psql -l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
 postgres  | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
 template0 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
(3 rows)


建立資料庫:

$ sudo -u postgres createdb MyDB

刪除資料庫:

$ sudo -u postgres dropdb MyDB

建立使用者(-P: 建立密碼, -l: 有登入權限[預設], -L: 禁止登入, -s: 超級使用者, -d: 建立資料庫權限):

$ sudo -u postgres createuser -P -s -d -l root
$ sudo -u postgres createuser -s -d -l user1
$ sudo -u postgres createuser -s -l user2
$ sudo -u postgres createuser -l user3
$ sudo -u postgres createuser -d user4
$ sudo -u postgres createuser -s user5


列出使用者列表:

$ sudo -u postgres psql -c "SELECT * FROM pg_user";
 usename  | usesysid | usecreatedb | usesuper | usecatupd | userepl |  passwd  | valuntil | useconfig
----------+----------+-------------+----------+-----------+---------+----------+----------+-----------
 postgres |       10 | t           | t        | t         | t       | ******** |          |
 user1    |    ####1 | t           | t        | t         | f       | ******** |          |
 user2    |    ####2 | t           | t        | t         | f       | ******** |          |
 user3    |    ####3 | f           | f        | f         | f       | ******** |          |
 user4    |    ####4 | t           | f        | f         | f       | ******** |          |
 user5    |    ####5 | t           | t        | t         | f       | ******** |          |
 root     |    ####6 | t           | t        | t         | f       | ******** |          |
(7 rows)


刪除帳號:

$ sudo -u postgres dropuser user1
$ sudo -u postgres dropuser user2
$ sudo -u postgres dropuser user3
$ sudo -u postgres dropuser user4
$ sudo -u postgres dropuser user5


遠端登入資料庫(資料庫跟使用者必須先建立):

$ sudo -u postgres createdb helloworld
$ sudo -u postgres createuser -P -l user1
$ sudo -u postgres createuser -P -L user2

$ psql -h localhost -U user1
Password for user user1:
psql: FATAL:  database "user1" does not exist

$ psql -h localhost -U user1 -d helloworld
Password for user user1:
psql (9.4.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

helloworld=>\q

$ psql -h localhost -U user2 -d helloworld
Password for user user2:
psql: FATAL:  role "user2" is not permitted to log in


以下皆在 helloworld 資料庫行動:

$ psql -h localhost -U user1 -d helloworld
Password for user user1:
psql (9.4.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

helloworld=>


建立資料表:

helloworld=> CREATE TABLE haha ( field1 INT, field2 VARCHAR(64) );
CREATE TABLE
helloworld=> CREATE TABLE hehe ( field1 TEXT );


列出資料表:

helloworld=> \d
       List of relations
 Schema | Name | Type  | Owner
--------+------+-------+-------
 public | haha | table | user1
 public | hehe | table | user1
(2 rows)


查詢資料表定義:

helloworld=> \d haha
            Table "public.haha"
 Column |         Type          | Modifiers
--------+-----------------------+-----------
 field1 | integer               |
 field2 | character varying(64) |


刪除資料表:

helloworld=> DDROP TABLE haha;
DROP TABLE


建立索引:

helloworld=> CREATE INDEX index_test ON hehe ( field1 );
helloworld=> \d hehe
    Table "public.hehe"
 Column | Type | Modifiers
--------+------+-----------
 field1 | text |
Indexes:
    "index_test" btree (field1)


刪除索引:

helloworld=> DROP INDEX index_test;
DROP INDEX
helloworld=> \d hehe
    Table "public.hehe"
 Column | Type | Modifiers
--------+------+-----------
 field1 | text |


建立 Partial Indexes:

helloworld=> CREATE INDEX index_substring ON hehe ( substring(field1, 0, 32) );
CREATE INDEX
helloworld=> CREATE INDEX index_condition ON hehe ( field1 )
helloworld->  WHERE substring(field1, 0, 1) = 'a' ;
CREATE INDEX
helloworld=> \d hehe
    Table "public.hehe"
 Column | Type | Modifiers
--------+------+-----------
 field1 | text |
Indexes:
    "index_condition" btree (field1) WHERE "substring"(field1, 0, 1) = 'a'::text
    "index_substring" btree ("substring"(field1, 0, 32))


從 STDIN 輸入資料至 helloworld.hehe 中:

$ bash --version
GNU bash, version 4.3.30(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ cat <<EOF | psql -h localhost -U user1 -W -d helloworld
> COPY hehe (field1) FROM stdin;
> 1
> 2
> 3
> 4
> 5
> 6
> EOF
Password for user user1:
COPY 6

$ psql -h localhost -U user1 -W -d helloworld -c 'SELECT * FROM hehe;'
Password for user user1:
 field1
--------
 1
 2
 3
 4
 5
 6
(6 rows)

2015年5月22日 星期五

Google API 筆記 - 使用 Google OAuth 2.0 API @ Ubuntu 14.04, PHP 5.5


準備替服務整合 Google Plus 登入方式,因此研究了一下 Google OAuth 2.0 API 的用法:
  • 在 Google Developer Console 建立一個 Google Project
  • 開啟此 Google Project 的 OAuth 使用,並設置"重新導向 URI",此處 URI 雖然不支援萬用符號,但支援輸入多筆,還算夠用。
整個過程下來,得到 client_id、client_secret 兩筆資料,如此一來就可以進行 OAuth 使用:

先組出 Google OAuth 所需的網址提供使用者授權,需要填寫 scope, response_type, redirect_uri, access_type, approval_prompt, client_id,例如:

$init_url = 'https://accounts.google.com/o/oauth2/auth?'. http_build_query( array(
'scope' => 'email profile',
'response_type' => 'code',
'redirect_uri' => $google_oauth_callback_url,
'access_type' => 'offline',
'approval_prompt' => 'force',
'client_id' => $google_project_client_id,
));
header('Location: '.$init_url);


接著,在 callback_url 的網頁中,接了 Google OAuth 回傳的 code 後,從 server site 發 requests 進行處理:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.googleapis.com/oauth2/v3/token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'client_id' => $google_project_client_id,
'client_secret' => $google_project_secret_key,
'code' => $google_ret_code,
'grant_type' => 'authorization_code',
'redirect_uri' => $google_oauth_callback_url,
)));
$ret = @json_decode(curl_exec($ch), true);


若一切順利的話,在 $ret 中,就可以拿到 access_token 了(此例需開啟 Google+ API)

$profile = @json_decode(file_get_contents('https://www.googleapis.com/plus/v1/people/me?'.http_build_query( array(
'access_token' => $ret['access_token']
))), true);

print_r($profile);


最後,若測試完後,想要替自己用的 Google account 刪除 app 授權的話,可以這邊刪除:

https://security.google.com/settings/security/permissions?pli=1

2014年4月26日 星期六

iOS 開發筆記 - Facebook SDK 與 App Reviewer 無法登入 FB 問題的處理心得



經過幾番測試,發現有些 iOS App Reviewer 無法正常登入 Facebook ,因此,假使你的 app 一開始就需要登入 FB 的話,很有可能會莫名其妙地收到 App Submission Feedback 的信件通知 Orz 然後百般跟 Reviewer 說會不會是他自己無法連到也沒法解決,千篇一律地收到制式回應,最後為了降低等待時間,就是重新上傳 binary 等待下一個 App Reviewer 了

故最佳解就是先限制 app 可以使用的地區,例如美國等,以此避免某些 App Reviewer 所在的地區不能連 Facebook !

2014年3月26日 星期三

[Linux] 限制使用者只能用 public key 登入 @ Ubuntu 12.04

原先是要用在 mysql 之 rsync 的備份方式,雖然最後不用了,也順便記一下:

$ sudo vim /etc/ssh/sshd_config
#PasswordAuthentication yes # default
RSAAuthentication yes
PubkeyAuthentication yes

Match User user1,user2
PasswordAuthentication no


$ sudo service ssh reload