2022年11月10日 星期四

Spring Boot 啟用 mTLS

最近在幫專案導入 Spring Boot,導入時比較麻煩的地方,是要保持專案的權限控制,也就是要讓它依然能夠正常以 Mutual TLS 的方式進行驗證。雖然在這個狀況下,往好處想是我們已經有現成的測試程序,能夠用來驗證是否 Mutual TLS 有正常運作,不過當驗證異常的時候,要找出問題還是蠻費工夫的…。這篇會稍微紀錄一點相關的資訊。

什麼是 Mutual TLS?

Mutual TLS(或者簡稱 mTLS)是驗證範圍更寬一點的 HTTPS,一般討論 HTTPS 時,比較常見的討論對象是單向的 TLS,也就是讓客戶端驗證伺服器是否真的是它宣稱的伺服器。而 Mutual TLS 則是雙向的 TLS,不但客戶端要驗證伺服器、伺服器也要驗證客戶端。換句話說,伺服器和客戶端都需要有私鑰和憑證。

TLS 相關名詞解釋

TLS 在運作時,會需要有公鑰(public key)和私鑰(private key),這裡就不解釋各自的用途了。實際 TLS 在運作時,通常會以私鑰和憑證(certificate)的形式在表示,其中憑證的內容是包含了公鑰、發行者(Issuer)和有效時間等資訊的檔案。也就是說,憑證會拿來提供給對方做驗證。

在使用 curl 時,會注意到 curl 允許的 --key--cert 的格式是 PEM,這是因為 curl 就只支援 PEM 格式。不過在 Java 的世界中,比較麻煩的是 Java 一般不支援 PEM,所以網路上的文章常常會看到有使用 keytool 的流程,這個流程是為了把 PEM 格式的私鑰和憑證,轉換成 PKCS #12(.p12)或者 Java KeyStore 格式(.jks)的檔案。可以參考公鑰密碼學標準

除了 KeyStore 以外,另外還會有個 TrustStore 的玩意兒。兩個的功能剛好相反,KeyStore 是用來讓別人做驗證、TrustStore 的功能則是用來驗證別人。所以說,KeyStore 的產生會使用自己的私鑰和憑證,這是因為產生出來的 KeyStore 內容也會包含自己的憑證資訊。

設定 Spring Boot 的 SSL

具體設定方法,可以參考官方文件中 How-to 章節的 SSL 部份。不過我個人其實覺得官方文件寫得還挺籠統的…。

實際我自己在設定時,在 Spring Boot + Jetty 底下有遇到幾種奇怪的狀況:

  1. 使用 keystore(JKS) 和 truststore,然後 Spring Boot 顯示有正常啟動 HTTPS,但結果用 curl 或 openssl 檢查時,都檢查不到有 HTTPS。
  2. 使用 PEM,然後 HTTPS 正常啟動,憑證也能正常被客戶端讀取,但 mTLS 一直失敗。

最後這裡直接紀錄目前會成功的版本,是採用 PKCS12 的格式讓 Spring Boot 讀取。

server.port=8443
server.ssl.enabled=true
server.ssl.key-store=/path/to/key/store/my_keystore.p12  # PKCS12 檔案的路徑
server.ssl.key-alias=accio  # 別名,在產生 PKCS12 檔的時候指定的名字
server.ssl.key-store-password=mypassword  # PKCS12 的密碼
server.ssl.trust-store=/path/to/trust/store/my_truststore.jks  # trust store 檔案的路徑
server.ssl.trust-store-type=JCEKS  # trust store 檔案的格式
server.ssl.trust-store-password=mypassword  # trust store 的密碼
server.ssl.client-auth=want  # 啟用 mTLS,但不強制

如何檢查 HTTPS?

使用 curl 或者 openssl 都可以做檢查 HTTPS。

openssl

openssl 時,就是讓 openssl 模擬成 client,指令如下:

openssl s_client -connect localhost:8443

以我的狀況,如果 HTTPS 啟動但有問題 (?) 的話,會出現以下這樣的訊息。

[root@a976d6ac5637 ~]# openssl s_client -connect localhost:8443
CONNECTED(00000003)
140387372447632:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:769:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 289 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID:
    Session-ID-ctx:
    Master-Key:
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1667406122
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

訊息中可以明確看出,openssl 並沒有找到合適的 cipher 能夠進行加密連線。

而如果是正常的 HTTPS 的話,會像是這樣:

[root@a976d6ac5637 ~]# openssl s_client -connect google.com:443
CONNECTED(00000005)
depth=3 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=2 C = US, O = Google Trust Services LLC, CN = GTS Root R1
verify return:1
depth=1 C = US, O = Google Trust Services LLC, CN = GTS CA 1C3
verify return:1
depth=0 CN = *.google.com
verify return:1
---
Certificate chain
 0 s:/CN=*.google.com
   i:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
 1 s:/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
   i:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
 2 s:/C=US/O=Google Trust Services LLC/CN=GTS Root R1
   i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
---
Server certificate
-----BEGIN CERTIFICATE-----
....(ignored)....
-----END CERTIFICATE-----
subject=/CN=*.google.com
issuer=/C=US/O=Google Trust Services LLC/CN=GTS CA 1C3
---
No client certificate CA names sent
Server Temp Key: ECDH, X25519, 253 bits
---
SSL handshake has read 7270 bytes and written 281 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-CHACHA20-POLY1305
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-CHACHA20-POLY1305
    Session-ID: D0F1CB0DAE17297143F933D8C93BB905D088849CAAFED06698803C8F14367771
    Session-ID-ctx:
    Master-Key: ADC789FEFEF3A3747E6741D23B090CAA3E1BBEB8B6DED6EB44910E45257F9C977378CCAB42C387828EDC2CC8D4B91059
    TLS session ticket lifetime hint: 100800 (seconds)
    TLS session ticket:
    ....(ignored)....

    Start Time: 1668083091
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---

部分細節我就直接刪掉了,不過可以看出正常狀況它應該要能解析出 server 的憑證內容,並且顯示使用什麼協定來跟伺服器溝通。

附錄:Jetty 的 log

由於我用的是 Jetty,因此如果想追 Jetty 這邊的 log,可以打開 org.eclipse.jetty 的 debug log,從訊息中可以看出,Jetty 在啟用 HTTPS 時,會建立 SslContextFactory 的物件來進行(廢話)。更具體來說,應該會是 SslContextFactory.Server 物件。雖然說,出現這段看起來有啟動 HTTPS 的 log,實際上並不代表 HTTPS 就有正常被啟動了。我的實驗過程有遇到一個情況是 log 顯示 HTTPS 似乎啟動了,但結果真的用 curlopenssl 打都找不到憑證…。

2022-11-02 15:48:15,603 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - starting SslConnectionFactory@40ee0a22{SSL->HTTP/1.1}
2022-11-02 15:48:15,603 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - starting Server@7bde1f3a[provider=null,keyStore=file:////path/to/key/store/my_keystore.p12,trustStore=file:////path/to/trust/store/my_truststore.jks]
2022-11-02 15:48:15,636 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - managers=[org.eclipse.jetty.util.ssl.AliasedX509ExtendedKeyManager@5cc152f9] for Server@7bde1f3a[provider=null,keyStore=file:////path/to/key/store/my_keystore.p12,trustStore=file:////path/to/trust/store/my_truststore.jks]
2022-11-02 15:48:15,640 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Selected Protocols [TLSv1.3, TLSv1.2] of [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1, SSLv3, SSLv2Hello]
2022-11-02 15:48:15,640 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Selected Ciphers   [TLS_AES_256_GCM_SHA384, ....]
2022-11-02 15:48:15,640 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Customize sun.security.ssl.SSLEngineImpl@1117cc7c
2022-11-02 15:48:15,641 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - STARTED @119497ms Server@7bde1f3a[provider=null,keyStore=file:////path/to/key/store/my_keystore.p12,trustStore=file:////path/to/trust/store/my_truststore.jks]
2022-11-02 15:48:15,641 [main] DEBUG org.eclipse.jetty.util.ssl.SslContextFactory - Customize sun.security.ssl.SSLEngineImpl@4797023d
2022-11-02 15:48:15,641 [main] DEBUG org.eclipse.jetty.util.component.AbstractLifeCycle - STARTED @119497ms SslConnectionFactory@40ee0a22{SSL->HTTP/1.1}

2022年10月23日 星期日

Markdown Here 樣式筆記

紀錄目前 Markdown Here plugin 使用的設定。

Syntax Highlight 模板:Monokai Sublime

CSS:
這裡主要是模板以外再複寫一次 blockquote 的語法,讓部落格本來就在用的 blockquote 樣式維持原狀。

/*
 * NOTE:
 * - The use of browser-specific styles (-moz-, -webkit-) should be avoided.
 *   If used, they may not render correctly for people reading the email in
 *   a different browser than the one from which the email was sent.
 * - The use of state-dependent styles (like a:hover) don't work because they
 *   don't match at the time the styles are made explicit. (In email, styles
 *   must be explicitly applied to all elements -- stylesheets get stripped.)
 */

/* This is the overall wrapper, it should be treated as the `body` section. */
.markdown-here-wrapper {
}

/* To add site specific rules, you can use the `data-md-url` attribute that we
   add to the wrapper element. Note that rules like this are used depending
   on the URL you're *sending* from, not the URL where the recipient views it.
*/
/* .markdown-here-wrapper[data-md-url*="mail.yahoo."] ul { color: red; } */

pre, code {
  font-size: 0.85em;
  font-family: Consolas, Inconsolata, Courier, monospace;
}

code {
  margin: 0 0.15em;
  padding: 0 0.3em;
  white-space: pre-wrap;
  border: 1px solid #EAEAEA;
  background-color: #F8F8F8;
  border-radius: 3px;
  display: inline; /* added to fix Yahoo block display of inline code */
}

pre {
  font-size: 1em;
  line-height: 1.2em;
}

pre code {
  white-space: pre;
  overflow: auto; /* fixes issue #70: Firefox/Thunderbird: Code blocks with horizontal scroll would have bad background colour */
  border-radius: 3px;
  border: 1px solid #CCC;
  padding: 0.5em 0.7em;
  display: block !important; /* added to counteract the Yahoo-specific `code` rule; without this, code blocks in Blogger are broken */
}

/* In edit mode, Wordpress uses a `* { font: ...;} rule+style that makes highlighted
code look non-monospace. This rule will override it. */
.markdown-here-wrapper[data-md-url*="wordpress."] code span {
  font: inherit;
}

/* Wordpress adds a grey background to `pre` elements that doesn't go well with
our syntax highlighting. */
.markdown-here-wrapper[data-md-url*="wordpress."] pre {
  background-color: transparent;
}

/* This spacing has been tweaked to closely match Gmail+Chrome "paragraph" (two line breaks) spacing.
Note that we only use a top margin and not a bottom margin -- this prevents the
"blank line" look at the top of the email (issue #243).
*/
p {
  /* !important is needed here because Hotmail/Outlook.com uses !important to
     kill the margin in <p>. We need this to win. */
  margin: 0 0 1.2em 0 !important;
}

table, pre, dl, blockquote, q, ul, ol {
  margin: 1.2em 0;
}

ul, ol {
  padding-left: 2em;
}

li {
  margin: 0.5em 0;
}

/* Space paragraphs in a list the same as the list itself. */
li p {
  /* Needs !important to override rule above. */
  margin: 0.5em 0 !important;
}

/* Smaller spacing for sub-lists */
ul ul, ul ol, ol ul, ol ol {
  margin: 0;
  padding-left: 1em;
}

/* Use Roman numerals for sub-ordered-lists. (Like Github.) */
ol ol, ul ol {
  list-style-type: lower-roman;
}

/* Use letters for sub-sub-ordered lists. (Like Github.) */
ul ul ol, ul ol ol, ol ul ol, ol ol ol {
  list-style-type: lower-alpha;
}

dl {
  padding: 0;
}

dl dt {
  font-size: 1em;
  font-weight: bold;
  font-style: italic;
}

dl dd {
  margin: 0 0 1em;
  padding: 0 1em;
}

blockquote, q {
  border-left: 4px solid #DDD;
  padding: 0 1em;
  color: #777;
  quotes: none;
}

blockquote::before, blockquote::after, q::before, q::after {
  content: none;
}

blockquote{border-left: 15px solid #c76c0c;font-family: Georgia, serif;font-size:15px;text-align: justify;background: #fff;line-height: 1.2;
display:block;margin: 0 0 20px;padding: 15px 20px 15px 45px;position: relative; font-size: 16px;color: #666;
border-right: 2px solid #c76c0c;box-shadow: 2px 2px 15px #ccc;-webkit-box-shadow: 2px 2px 15px #ccc;-moz-box-shadow: 2px 2px 15px #ccc;}
blockquote::before{font-size: 50px;position: absolute;color: #999;left: 10px;top:5px;font-weight: bold;content: "\201C";}
blockquote a{cursor: pointer;color: #c76c0c;text-decoration: none;background: #eee;padding: 0 3px;}
blockquote a:hover{color: #555;}

h1, h2, h3, h4, h5, h6 {
  margin: 1.3em 0 1em;
  padding: 0;
  font-weight: bold;
}

h1 {
  font-size: 1.6em;
  border-bottom: 1px solid #ddd;
}

h2 {
  font-size: 1.4em;
  border-bottom: 1px solid #eee;
}

h3 {
  font-size: 1.3em;
}

h4 {
  font-size: 1.2em;
}

h5 {
  font-size: 1em;
}

h6 {
  font-size: 1em;
  color: #777;
}

table {
  padding: 0;
  border-collapse: collapse;
  border-spacing: 0;
  font-size: 1em;
  font: inherit;
  border: 0;
}

tbody {
  margin: 0;
  padding: 0;
  border: 0;
}

table tr {
  border: 0;
  border-top: 1px solid #CCC;
  background-color: white;
  margin: 0;
  padding: 0;
}

table tr:nth-child(2n) {
  background-color: #F8F8F8;
}

table tr th, table tr td {
  font-size: 1em;
  border: 1px solid #CCC;
  margin: 0;
  padding: 0.5em 1em;
}

table tr th {
 font-weight: bold;
  background-color: #F0F0F0;
}

2022年9月4日 星期日

[筆記] 詞彙的建立

在 IR 系統的前處理中,因為我們想要把資料建成 posting list,會需要先有相關的詞彙,才有辦法依據詞彙建立相應的 posting list。不過這時會有幾個常見的問題需要處理。

這篇會簡要紀錄一下書中提到的考量點。

1. Tokenization

Tokenization 是把句子分割並萃取的技術,萃取出來的單位被稱為 token,或者也可能被稱為 word、term,但事實上有時候我們可能會想要把 token 跟其他名詞做一點定義上的區分。以下是書中對於 token 的定義:

A token is an instance of a sequence of characters in some particular document that are grouped together as a useful semantic unit for processing.

除了 token 以外,另外還有 typeterm 這兩個名詞。書中提供的定義分別如下:

A type is the class of all tokens containing the same character sequence.

A term is a (perhaps normalized) type that is included in the IR system’s dictionary.

直接用書中提供的例子應該會比較好懂。舉例來說,如果我們想對下述的句子做 tokenization:

to sleep perchance to dream

此時,會有 to, sleep, perchance, to, dream 共 5 個 token;但只會有 4 個 type:to, sleep, perchance, dream,因為有兩個 to 是重複的;而如果 IR 系統中有處理 stop words,我們可能會把 to 視為是 stop words 而將它移除,此時最終在 IR 系統中被 index 的 term 就只會有 3 個:sleep, perchance, dream

那麼我們通常會如何做 tokenziation 呢?初步看起來,似乎就是直接針對空白做分割,然後可能把標點符號什麼的都去除掉就可以了?然而現實上,還是會出現一些比較困難的問題,導致我們可能還需要考慮別的因子。例如:

  • ' 在英文中,可以代表所有格關係、也可以代表縮寫。
  • 不同語言有自己的語言特徵。
  • 特定領域可能有自己特殊的 type。
  • - 在英文中也存在多種不同用法。
  • 多個字組成的詞彙無法單純用空白切割來處理。

以下會分別針對這些狀況做點簡單的描述。

1.1. Apostrophe (‘)

' 這個符號在英文中有多種用法,例如以下這個句子:

Mr. O'Neill thinks that the boys' stories about Chile's capital aren't amusing.

這裡同時出現了三種不同的用法:

  • O’Neill 中的 ' 是名字的一部分。
  • boys’ 和 Chile’s 中的 ' 是所有格。
  • aren’t 中的 ' 是縮寫。

此時針對 ' 做的處理的不同,可能會導致後續 IR 系統的 matching 受到影響。例如如果我們將 ' 拆開的話,aren't 被拆成 arent 看起來就會很奇怪。同時,O'Neill 被拆成 ONeill 會導致搜尋時使用 o'neil 會無法 match 到相應的 token。

1.2. 語言特徵

tokenization 的行為往往跟語言有很大的關係,不同語言的 tokenization 作法會有蠻大的不同。例如中文和日文就沒有空白分隔的特性,每個字都是連在一起的。

1.3. 領域特徵

在特定領域,有可能有領域內專屬的詞彙需要能夠被識別成 term,例如 C++C#、或者飛機型號 B-52(戰略轟炸機)。另外網際網路中的 URL(https://xxx)、e-mail(abc@abc.com)、IP 位址(a.b.c.d)等等可能也會需要能夠被視為是獨立的 term。

1.4. 連字符號 Hyphenation (-)

英文中連字符號的處理可能會很複雜,書中舉出幾種連字符號的用法:

  • 分隔單字之間的母音,像是 co-education。這個例子實際上它代表的是 coeducaion
  • 將多個名詞連接成一個新的名稱,例如 Hewlett-Packard
  • 多個詞語的組成結果,例如 the hold-him-back-and-drag-him-away maneuver。這個例子應該要被拆解成各自獨立的 term。

1.5. 特殊詞彙

這類的資訊其實我覺得也可以算是領域特徵的一種。例如書中舉的一個例子是 New York University(紐約大學)跟 York University(約克大學)。如果他們沒有被獨立視為 term,而是純粹依據空白拆開的話,那麼搜尋 York University 時就會 match 到 New York University 了,但這顯然並非是使用者想找的東西。

2. Stop Words

停用詞(Stop Words)的目的,是去除出現頻率過高、但又跟文件本身關係不大的詞彙。不過由於去除停用詞的行為,有時會導致意義的流失,所以現代的 IR 系統比較會考慮不去除停用詞,改為將停用詞賦予較低的權重,使其影響變小。舉例來說,flights to London 如果把 to 給去除了,flights to London 的意義就流失了。


其實還有兩個小節,不過等有空時再補筆記了…。
是說搜尋是個在現代非常常見的行為,但最近的研究總覺得這個領域有點冷門?