<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2023-03-01T13:50:06+00:00</updated><id>/feed.xml</id><title type="html">Gaius’ Blog</title><author><name>gaius</name></author><entry><title type="html">Self-signed certificate</title><link href="/devops/self-signed-certificate.html" rel="alternate" type="text/html" title="Self-signed certificate" /><published>2021-02-10T00:00:00+00:00</published><updated>2021-02-10T00:00:00+00:00</updated><id>/devops/self-signed-certificate</id><content type="html" xml:base="/devops/self-signed-certificate.html"><![CDATA[<h2 id="介绍">介绍</h2>

<p>CA 提供证书保证传输信息安全性。当然个人也可以扮演 CA，但客户端这时是不信任的, 需要将 CA 证书，即 CA 公钥集成在客户端内。</p>

<h2 id="信息安全">信息安全</h2>
<p>信息传输过程中需要保证的安全问题有：信息的保密性、信息的安全性以及双方身份的识别。</p>

<h3 id="信息保密性">信息保密性</h3>
<p>需要两个密钥对，非对称加密密钥对 A 和对称加密密钥 B。客户端使用 A 的公钥对 B 的密钥进行加密生成 C。然后将 C 传输给服务端，服务端使用 A 的私钥对 C 进行解密，得到 B 的密钥。
则客户端和服务端都存在 B 的密钥，则可以在传输信息前，通过非对称加密密钥 B 进行信息加解密。A 的作用是保障 B 的安全传输，B 的作用是保障信息的安全传输。</p>

<h3 id="信息完整性">信息完整性</h3>
<p>需要非对称加密密钥对 B。客户端使用散列算法计算传输内容的 hash 值，即为摘要 H1。然后使用 B 的公钥对摘要进行加密, 生成加密后的摘要 C。将 C 传输给服务端，服务端用 B 的私钥对 C 进行解密, 则得到摘要 H1。
服务端再将传输过来的内容, 使用客户端相同的散列算法计算其 hash 值, 即为摘要 H2, 则比较 H1 和 H2 的值来保证信息的完整性。</p>

<h3 id="双方身份的识别">双方身份的识别</h3>
<p>需要非对称加密密钥对 B。服务端将 B 的公钥发送给客户端，服务端再将自己的身份内容使用 B 的私钥进行加密发送给客户端。客户端使用 B 的公钥进行解密就能认证服务端的身份。
当然这种情况可能会被第三方劫持。第三方劫持就是服务端发送给客户端信息和 B 的公钥，但中间被三方劫持到 B 的公钥。然后三方再自己创建非对称加密密钥对 C，用来和客户端交互加解密使用。则服务端发来 B 私钥加密内容，三方劫持后使用 B 公钥进行解密，然后将信息以及 C 的公钥发送给客户端。则客户端用 C 的公钥进行加密信息，三方劫持使用 C 的私钥进行解密。</p>

<h2 id="数字证书">数字证书</h2>
<p>数字证书本身解决的就是双方身份识别/身份认证的问题, 本身是由 CA 进行签发的，CA 可以是权威厂商，也可以是个人签发。区别在于权威厂商的 CA 公钥默认集成在客户端，而个人签发需要将 CA 公钥手动集成到客户端。CA 作用只是签发服务端证书或下级 CA 证书。服务端证书才是真正信息传输加密过程中使用的。</p>

<p>数字证书主要分为三部分:</p>
<ul>
  <li>证书内容(即身份信息, 例如发行机构相关信息) C</li>
  <li>散列算法 A</li>
  <li>加密过的密文 S</li>
</ul>

<p>数字证书认证主要过程为:</p>
<ol>
  <li>服务端将证书内容 C 通过散列算法 A 计算出 hash，即摘要。然后通过 CA 的私钥进行加密即生成加密过的密文 S。</li>
  <li>客户端发起请求时，服务端会将数字证书发送给客户端。客户端通过 CA 的公钥对数字证书的 S 进行解密得到摘要 H1。</li>
  <li>客户端将证书内容通过散列算法 A 计算出 hash，即摘要 H2。</li>
  <li>对比摘要 H1 和 H2 是否相等，如果相等则客户端就认证了服务端的身份。</li>
</ol>

<h2 id="自签证书生成">自签证书生成</h2>

<h3 id="step1-自签发-ca">Step1 自签发 CA</h3>

<p>生成 CA 私钥</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl genrsa <span class="nt">-out</span> ca.key 2048
</code></pre></div></div>

<p>创建 openssl 配置文件 <code class="language-plaintext highlighter-rouge">openssl.conf</code>, 注意 <code class="language-plaintext highlighter-rouge">basicConstraints</code> 的 CA 值为 <code class="language-plaintext highlighter-rouge">TRUE</code>，表示其为 CA 证书用来签发用的，不论几级 CA 证书 CA 值都为 <code class="language-plaintext highlighter-rouge">TRUE</code>。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ req ]
#default_bits		= 2048
#default_md		= sha256
#default_keyfile 	= privkey.pem
distinguished_name	= req_distinguished_name
attributes		= req_attributes
extensions               = v3_ca
req_extensions           = v3_ca

[ req_distinguished_name ]
countryName			= Country Name (2 letter code)
countryName_min			= 2
countryName_max			= 2
stateOrProvinceName		= State or Province Name (full name)
localityName			= Locality Name (eg, city)
0.organizationName		= Organization Name (eg, company)
organizationalUnitName		= Organizational Unit Name (eg, section)
commonName			= Common Name (eg, fully qualified host name)
commonName_max			= 64
emailAddress			= Email Address
emailAddress_max		= 64

[ req_attributes ]
challengePassword		= A challenge password
challengePassword_min		= 4
challengePassword_max		= 20

[ v3_ca ]
basicConstraints         = CA:TRUE
</code></pre></div></div>

<p>用 <code class="language-plaintext highlighter-rouge">openssl.conf</code> 配置文件在加上私钥生成自签名的 CA 证书, 即 CA 的公钥。用作客户端解密服务端的密文 S 的。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req <span class="nt">-new</span> <span class="nt">-key</span> ca.key <span class="nt">-nodes</span> <span class="nt">-out</span> ca.csr <span class="nt">-config</span> openssl.conf
openssl x509 <span class="nt">-req</span> <span class="nt">-days</span> 36500 <span class="nt">-extfile</span> openssl.conf <span class="nt">-extensions</span> v3_ca <span class="nt">-in</span> ca.csr <span class="nt">-signkey</span> ca.key <span class="nt">-out</span> ca.crt
</code></pre></div></div>

<p>查看生成 CA 证书详细内容。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl x509 <span class="nt">-in</span> ca.crt <span class="nt">-text</span> <span class="nt">-noout</span>
</code></pre></div></div>

<h3 id="step2-使用自签发-ca签发服务端证书">Step2 使用自签发 CA，签发服务端证书</h3>

<p>生成服务端私钥</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl genrsa <span class="nt">-out</span> sca.key 2048
</code></pre></div></div>

<p>生成服务端身份信息 CSR 文件</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl req <span class="nt">-new</span> <span class="nt">-key</span> sca.key <span class="nt">-out</span> sca.csr
</code></pre></div></div>

<p>生成服务端证书。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl x509 <span class="nt">-req</span> <span class="nt">-in</span> sca.csr <span class="nt">-CA</span> ca.crt <span class="nt">-CAkey</span> ca.key <span class="nt">-CAcreateserial</span> <span class="nt">-out</span> sca.crt <span class="nt">-days</span> 36500
</code></pre></div></div>

<h3 id="step3-客户端集成-ca-公钥">Step3 客户端集成 CA 公钥</h3>
<p>这里的 CA 公钥即为 CA 的证书。</p>]]></content><author><name>Gaius</name></author><category term="Devops" /><category term="documentation" /><summary type="html"><![CDATA[介绍]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/self-signed-certificate.jpg" /><media:content medium="image" url="/self-signed-certificate.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Circuit Breaker Pattern</title><link href="/design/pattern/circuit-breaker-pattern.html" rel="alternate" type="text/html" title="Circuit Breaker Pattern" /><published>2020-11-18T00:00:00+00:00</published><updated>2020-11-18T00:00:00+00:00</updated><id>/design/pattern/circuit-breaker-pattern</id><content type="html" xml:base="/design/pattern/circuit-breaker-pattern.html"><![CDATA[<h2 id="介绍">介绍</h2>

<p>熔断模式，类比现实当中电路熔断机制。当线路电压过高时会保险丝会熔断, 维修成功后可进行恢复供电。分布式场景下也会面临服务异常以及网络超时等问题，需要一定时间进行恢复。如果一直进行重试请求，在未恢复的这段时间内都会返回失败，并且占用资源。所以需要熔断模式的设计核心为 “防止无限重试一个已知的失败操作” 。</p>

<h2 id="原理">原理</h2>

<p>熔断器相当于 Proxy，检测请求成功率当达到一定阈值时通过切换状态，给出正确的处理方式。</p>

<p>熔断器模式使用状态机进行实现，主要有以下三种状态:</p>

<ul>
  <li>Closed: 默认状态，允许操作执行。会监听操作失败次数，当达到阈值时熔断器转换为 Open 状态。</li>
  <li>Open: 执行操作失败会抛出异常，且会根据提前设置好恢复需要时间进行计时。当超过恢复时间时切换为 Half-Open 状态。</li>
  <li>Half-Open: 允许执行一定次数操作，如果全部成功即认为故障恢复，切换为 Closed 状态。如果有一次失败操作，则认为故障未恢复，转换为 Open 状态。</li>
</ul>

<h2 id="gobreaker">gobreaker</h2>

<p>Sony 开源项目 <a href="https://github.com/sony/gobreaker">gobreaker</a> 使用状态机实现熔断模式。源码实现总共 350 行相对比较简单。</p>

<p>熔断器 Struct</p>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">Settings</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">Name</span>          <span class="kt">string</span>
	<span class="n">MaxRequests</span>   <span class="kt">uint32</span>
	<span class="n">Interval</span>      <span class="n">time</span><span class="o">.</span><span class="n">Duration</span>
	<span class="n">Timeout</span>       <span class="n">time</span><span class="o">.</span><span class="n">Duration</span>
	<span class="n">ReadyToTrip</span>   <span class="k">func</span><span class="p">(</span><span class="n">counts</span> <span class="n">Counts</span><span class="p">)</span> <span class="kt">bool</span>
	<span class="n">OnStateChange</span> <span class="k">func</span><span class="p">(</span><span class="n">name</span> <span class="kt">string</span><span class="p">,</span> <span class="n">from</span> <span class="n">State</span><span class="p">,</span> <span class="n">to</span> <span class="n">State</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>熔断器创建以及配置</p>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">Settings</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">Name</span>          <span class="kt">string</span>
	<span class="n">MaxRequests</span>   <span class="kt">uint32</span>
	<span class="n">Interval</span>      <span class="n">time</span><span class="o">.</span><span class="n">Duration</span>
	<span class="n">Timeout</span>       <span class="n">time</span><span class="o">.</span><span class="n">Duration</span>
	<span class="n">ReadyToTrip</span>   <span class="k">func</span><span class="p">(</span><span class="n">counts</span> <span class="n">Counts</span><span class="p">)</span> <span class="kt">bool</span>
	<span class="n">OnStateChange</span> <span class="k">func</span><span class="p">(</span><span class="n">name</span> <span class="kt">string</span><span class="p">,</span> <span class="n">from</span> <span class="n">State</span><span class="p">,</span> <span class="n">to</span> <span class="n">State</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">NewCircuitBreaker</span><span class="p">(</span><span class="n">st</span> <span class="n">Settings</span><span class="p">)</span> <span class="o">*</span><span class="n">CircuitBreaker</span> <span class="p">{</span>
	<span class="n">cb</span> <span class="o">:=</span> <span class="nb">new</span><span class="p">(</span><span class="n">CircuitBreaker</span><span class="p">)</span>

	<span class="n">cb</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">Name</span>
	<span class="n">cb</span><span class="o">.</span><span class="n">onStateChange</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">OnStateChange</span>

	<span class="k">if</span> <span class="n">st</span><span class="o">.</span><span class="n">MaxRequests</span> <span class="o">==</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">maxRequests</span> <span class="o">=</span> <span class="m">1</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">maxRequests</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">MaxRequests</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">st</span><span class="o">.</span><span class="n">Interval</span> <span class="o">&lt;=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">interval</span> <span class="o">=</span> <span class="n">defaultInterval</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">interval</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">Interval</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">st</span><span class="o">.</span><span class="n">Timeout</span> <span class="o">&lt;=</span> <span class="m">0</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">defaultTimeout</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">Timeout</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="n">st</span><span class="o">.</span><span class="n">ReadyToTrip</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">readyToTrip</span> <span class="o">=</span> <span class="n">defaultReadyToTrip</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">readyToTrip</span> <span class="o">=</span> <span class="n">st</span><span class="o">.</span><span class="n">ReadyToTrip</span>
	<span class="p">}</span>

	<span class="n">cb</span><span class="o">.</span><span class="n">toNewGeneration</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">())</span>

	<span class="k">return</span> <span class="n">cb</span>
<span class="p">}</span>
</code></pre></div></div>

<p>执行函数主要分为三个阶段: <code class="language-plaintext highlighter-rouge">beforeRequest</code>、<code class="language-plaintext highlighter-rouge">Execute</code> 以及 <code class="language-plaintext highlighter-rouge">afterRequest</code></p>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">Execute</span><span class="p">(</span><span class="n">req</span> <span class="k">func</span><span class="p">()</span> <span class="p">(</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">))</span> <span class="p">(</span><span class="k">interface</span><span class="p">{},</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">generation</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">cb</span><span class="o">.</span><span class="n">beforeRequest</span><span class="p">()</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
	<span class="p">}</span>

	<span class="k">defer</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">e</span> <span class="o">:=</span> <span class="nb">recover</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">e</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">afterRequest</span><span class="p">(</span><span class="n">generation</span><span class="p">,</span> <span class="no">false</span><span class="p">)</span>
			<span class="nb">panic</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">}()</span>

	<span class="n">result</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">req</span><span class="p">()</span>
	<span class="n">cb</span><span class="o">.</span><span class="n">afterRequest</span><span class="p">(</span><span class="n">generation</span><span class="p">,</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span><span class="p">)</span>
	<span class="k">return</span> <span class="n">result</span><span class="p">,</span> <span class="n">err</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">toNewGeneration</code> 生成新的 generation，expiry 为过期时间。按照状态区分为:</p>

<ul>
  <li>Open: expiry 为当前时间加 Setting 中的 Timeout 恢复时间，</li>
  <li>Closed: expiry 为当前时间加 Setting 中的 Interval 一个监视周期时间。生成新的 generation 会清空 counts 内值。</li>
  <li>Half-Open: expiry 清零，通过 maxRequests 来判断。</li>
</ul>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">toNewGeneration</span><span class="p">(</span><span class="n">now</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">cb</span><span class="o">.</span><span class="n">generation</span><span class="o">++</span>
	<span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>

	<span class="k">var</span> <span class="n">zero</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span>
	<span class="k">switch</span> <span class="n">cb</span><span class="o">.</span><span class="n">state</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">StateClosed</span><span class="o">:</span>
		<span class="k">if</span> <span class="n">cb</span><span class="o">.</span><span class="n">interval</span> <span class="o">==</span> <span class="m">0</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">expiry</span> <span class="o">=</span> <span class="n">zero</span>
		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">expiry</span> <span class="o">=</span> <span class="n">now</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="n">cb</span><span class="o">.</span><span class="n">interval</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="k">case</span> <span class="n">StateOpen</span><span class="o">:</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">expiry</span> <span class="o">=</span> <span class="n">now</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="n">cb</span><span class="o">.</span><span class="n">timeout</span><span class="p">)</span>
	<span class="k">default</span><span class="o">:</span> <span class="c">// StateHalfOpen</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">expiry</span> <span class="o">=</span> <span class="n">zero</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">Counts</span><span class="p">)</span> <span class="n">clear</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">c</span><span class="o">.</span><span class="n">Requests</span> <span class="o">=</span> <span class="m">0</span>
	<span class="n">c</span><span class="o">.</span><span class="n">TotalSuccesses</span> <span class="o">=</span> <span class="m">0</span>
	<span class="n">c</span><span class="o">.</span><span class="n">TotalFailures</span> <span class="o">=</span> <span class="m">0</span>
	<span class="n">c</span><span class="o">.</span><span class="n">ConsecutiveSuccesses</span> <span class="o">=</span> <span class="m">0</span>
	<span class="n">c</span><span class="o">.</span><span class="n">ConsecutiveFailures</span> <span class="o">=</span> <span class="m">0</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">currentState</code> 获取当前状态, 按照状态区分为:</p>

<ul>
  <li>Closed: expiry 过期时间为 0 即达到一个监视周期时间则生成新的 generation, 清空 counts 内值。</li>
  <li>Open: expiry 过期时间为 0 时即到达恢复时间, 设置为 Half-Open 状态。</li>
</ul>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">currentState</span><span class="p">(</span><span class="n">now</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)</span> <span class="p">(</span><span class="n">State</span><span class="p">,</span> <span class="kt">uint64</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">switch</span> <span class="n">cb</span><span class="o">.</span><span class="n">state</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">StateClosed</span><span class="o">:</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">cb</span><span class="o">.</span><span class="n">expiry</span><span class="o">.</span><span class="n">IsZero</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">cb</span><span class="o">.</span><span class="n">expiry</span><span class="o">.</span><span class="n">Before</span><span class="p">(</span><span class="n">now</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">toNewGeneration</span><span class="p">(</span><span class="n">now</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="k">case</span> <span class="n">StateOpen</span><span class="o">:</span>
		<span class="k">if</span> <span class="n">cb</span><span class="o">.</span><span class="n">expiry</span><span class="o">.</span><span class="n">Before</span><span class="p">(</span><span class="n">now</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">setState</span><span class="p">(</span><span class="n">StateHalfOpen</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">cb</span><span class="o">.</span><span class="n">state</span><span class="p">,</span> <span class="n">cb</span><span class="o">.</span><span class="n">generation</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">beforeRequest</code> 给 Requests 变量加互斥锁, 防止竞争。<code class="language-plaintext highlighter-rouge">currentState</code> 获取当前状态, 通过熔断器三种状态执行不同操作:</p>

<ul>
  <li>Open: 直接抛错。</li>
  <li>Half-Open &amp;&amp; counts 中累计 Request 大于 Half-Open 状态 maxRequest 阈值: 直接返回错误。</li>
  <li>Closed &amp;&amp; Half-Open 且累计 Request 小于 Half-Open 状态 maxRequest 阈值: Request 数加一。</li>
</ul>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">beforeRequest</span><span class="p">()</span> <span class="p">(</span><span class="kt">uint64</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">cb</span><span class="o">.</span><span class="n">mutex</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
	<span class="k">defer</span> <span class="n">cb</span><span class="o">.</span><span class="n">mutex</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>

	<span class="n">now</span> <span class="o">:=</span> <span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span>
	<span class="n">state</span><span class="p">,</span> <span class="n">generation</span> <span class="o">:=</span> <span class="n">cb</span><span class="o">.</span><span class="n">currentState</span><span class="p">(</span><span class="n">now</span><span class="p">)</span>

	<span class="k">if</span> <span class="n">state</span> <span class="o">==</span> <span class="n">StateOpen</span> <span class="p">{</span>
		<span class="k">return</span> <span class="n">generation</span><span class="p">,</span> <span class="n">ErrOpenState</span>
	<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">state</span> <span class="o">==</span> <span class="n">StateHalfOpen</span> <span class="o">&amp;&amp;</span> <span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">Requests</span> <span class="o">&gt;=</span> <span class="n">cb</span><span class="o">.</span><span class="n">maxRequests</span> <span class="p">{</span>
		<span class="k">return</span> <span class="n">generation</span><span class="p">,</span> <span class="n">ErrTooManyRequests</span>
	<span class="p">}</span>

	<span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">onRequest</span><span class="p">()</span>
	<span class="k">return</span> <span class="n">generation</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">Counts</span><span class="p">)</span> <span class="n">onRequest</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">c</span><span class="o">.</span><span class="n">Requests</span><span class="o">++</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">afterRequest</code> 给 counts 变量加互斥锁, 防止竞争。操作执行后分为两种状态:</p>

<ul>
  <li>onSuccess: 当状态为 Closed 时则更改 count 计数, 当状态为 Half-Open 时则更改 count 计数且对比 ConsecutiveSuccesses 量即连续成功操作次数是否大于 maxRequest，如果大于则更改状态为 Closed。</li>
  <li>onFailure: 当状态为 Closed 时则更改 count 计数, readyToTrip 为 true 则状态变为 Open, 当状态为 Half-Open 状态变为 Open。</li>
</ul>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">afterRequest</span><span class="p">(</span><span class="n">before</span> <span class="kt">uint64</span><span class="p">,</span> <span class="n">success</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">cb</span><span class="o">.</span><span class="n">mutex</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
    <span class="k">defer</span> <span class="n">cb</span><span class="o">.</span><span class="n">mutex</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>

    <span class="n">now</span> <span class="o">:=</span> <span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span>
    <span class="n">state</span><span class="p">,</span> <span class="n">generation</span> <span class="o">:=</span> <span class="n">cb</span><span class="o">.</span><span class="n">currentState</span><span class="p">(</span><span class="n">now</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">generation</span> <span class="o">!=</span> <span class="n">before</span> <span class="p">{</span>
        <span class="k">return</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="n">success</span> <span class="p">{</span>
        <span class="n">cb</span><span class="o">.</span><span class="n">onSuccess</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">cb</span><span class="o">.</span><span class="n">onFailure</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">onSuccess</span><span class="p">(</span><span class="n">state</span> <span class="n">State</span><span class="p">,</span> <span class="n">now</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">switch</span> <span class="n">state</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">StateClosed</span><span class="o">:</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">onSuccess</span><span class="p">()</span>
	<span class="k">case</span> <span class="n">StateHalfOpen</span><span class="o">:</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">onSuccess</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">ConsecutiveSuccesses</span> <span class="o">&gt;=</span> <span class="n">cb</span><span class="o">.</span><span class="n">maxRequests</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">setState</span><span class="p">(</span><span class="n">StateClosed</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">Counts</span><span class="p">)</span> <span class="n">onSuccess</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">c</span><span class="o">.</span><span class="n">TotalSuccesses</span><span class="o">++</span>
	<span class="n">c</span><span class="o">.</span><span class="n">ConsecutiveSuccesses</span><span class="o">++</span>
	<span class="n">c</span><span class="o">.</span><span class="n">ConsecutiveFailures</span> <span class="o">=</span> <span class="m">0</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">cb</span> <span class="o">*</span><span class="n">CircuitBreaker</span><span class="p">)</span> <span class="n">onFailure</span><span class="p">(</span><span class="n">state</span> <span class="n">State</span><span class="p">,</span> <span class="n">now</span> <span class="n">time</span><span class="o">.</span><span class="n">Time</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">switch</span> <span class="n">state</span> <span class="p">{</span>
	<span class="k">case</span> <span class="n">StateClosed</span><span class="o">:</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="o">.</span><span class="n">onFailure</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">cb</span><span class="o">.</span><span class="n">readyToTrip</span><span class="p">(</span><span class="n">cb</span><span class="o">.</span><span class="n">counts</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">cb</span><span class="o">.</span><span class="n">setState</span><span class="p">(</span><span class="n">StateOpen</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
		<span class="p">}</span>
	<span class="k">case</span> <span class="n">StateHalfOpen</span><span class="o">:</span>
		<span class="n">cb</span><span class="o">.</span><span class="n">setState</span><span class="p">(</span><span class="n">StateOpen</span><span class="p">,</span> <span class="n">now</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">c</span> <span class="o">*</span><span class="n">Counts</span><span class="p">)</span> <span class="n">onFailure</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">c</span><span class="o">.</span><span class="n">TotalFailures</span><span class="o">++</span>
	<span class="n">c</span><span class="o">.</span><span class="n">ConsecutiveFailures</span><span class="o">++</span>
	<span class="n">c</span><span class="o">.</span><span class="n">ConsecutiveSuccesses</span> <span class="o">=</span> <span class="m">0</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Gaius</name></author><category term="Design" /><category term="pattern" /><category term="documentation" /><summary type="html"><![CDATA[介绍]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/circuit-breaker-pattern.jpg" /><media:content medium="image" url="/circuit-breaker-pattern.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">WWW &amp;amp; Root Record</title><link href="/devops/domain-with-www.html" rel="alternate" type="text/html" title="WWW &amp;amp; Root Record" /><published>2020-11-16T00:00:00+00:00</published><updated>2020-11-16T00:00:00+00:00</updated><id>/devops/domain-with-www</id><content type="html" xml:base="/devops/domain-with-www.html"><![CDATA[<h2 id="概念">概念</h2>

<h3 id="www">WWW</h3>

<p><a href="https://en.wikipedia.org/wiki/World_Wide_Web">万维网(World Wide Web)</a>是一个透过互联网访问的，由许多互相链接的超文本组成的系统。英国科学家蒂姆·伯纳斯-李于 1989 年发明了万维网。1990 年他在瑞士 CERN 的工作期间编写了第一个网页浏览器。网页浏览器于 1991 年在 CERN 以外发行，1991 年 1 月最先向其他研究机构发行，并于 1991 年 8 月在互联网上向公众开放。</p>

<h3 id="顶点记录">顶点记录</h3>

<p>顶点记录位于 DNS 区域的根(或顶点)中的 DNS 记录。 例如，在 DNS 区域 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 中，顶点记录还具有完全限定的名称 <code class="language-plaintext highlighter-rouge">airbnb.com</code>, 即称为裸域。按照惯例，相对名称 <code class="language-plaintext highlighter-rouge">@</code> 用于表示顶点记录。</p>

<h2 id="实践经验">实践经验</h2>

<h3 id="顶点记录--cname">顶点记录 &amp; CNAME</h3>

<p><a href="http://www.faqs.org/rfcs/rfc1034.html">RFC 1034 3.6.2</a> 给不推荐顶点记录添加 CNAME，需要直接配置 A 记录。万网的权威 DNS 服务，如果给顶点记录直接添加 CNAME 会直接报错。</p>

<p>主要原因是假如给 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 顶点记录添加 CNAME 至 <code class="language-plaintext highlighter-rouge">facebook.com</code>，则相当于给 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 添加 Alias <code class="language-plaintext highlighter-rouge">facebook.com</code>。则第一次访问 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 会把 <code class="language-plaintext highlighter-rouge">facebook.com</code> 记录本机缓存，下次访问 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 则直接使用本地缓存直接访问 <code class="language-plaintext highlighter-rouge">facebook.com</code>。</p>

<p>同时被 Alias 的还有 MX(Mail eXchange) 记录即邮箱服务记录, 也就导致给 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 发送邮件相当于给 <code class="language-plaintext highlighter-rouge">facebook.com</code> 发送邮件。而反过来先发送邮件，再访问网页则会给 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 发送邮件，因为本地没有缓存 CNAME。</p>

<p>正常访问逻辑应该是 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 网页访问到 <code class="language-plaintext highlighter-rouge">facebook.com</code>, 但是发送邮件到 <code class="language-plaintext highlighter-rouge">airbnb.com</code>。当添加 CNAME 给顶点记录时会导致邮箱服务出现问题。</p>

<p>当然类似 AWS、Azure 以及 Aliyun 等 DNS 服务是允许给顶点记录添加 CNAME 的，是因为基于 <a href="https://blog.cloudflare.com/introducing-cname-flattening-rfc-compliant-cnames-at-a-domains-root">CloudFlare</a> 方式。CloudFlare 原理为递归解析配置 CNAME 并转换成 A 记录。</p>

<p>正常顶点记录与 WWW DNS 配置方式为:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>airbnb.com        A   52.7.164.128
www.airbnb.com  CNAME airbnb.com
</code></pre></div></div>

<h3 id="浏览器访问特征">浏览器访问特征</h3>

<p>浏览器访问时会根据具体输入进行自动补全，查询不同的 DNS 记录。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://airbnb.com      =&gt; airbnb.com
https://www.airbnb.com  =&gt; www.airbnb.com
airbnb.com              =&gt; www.airbnb.com
www.airbnb.com          =&gt; www.airbnb.com
</code></pre></div></div>

<h3 id="tls-证书">TLS 证书</h3>

<p>WWW 解析记录可以看作顶点记录的一个子域，类似 <code class="language-plaintext highlighter-rouge">zh.airbnb.com</code> 与 <code class="language-plaintext highlighter-rouge">www.airbnb.com</code> 在 DNS 中是等价的。所以 <code class="language-plaintext highlighter-rouge">zh.airbnb.com</code> 和 <code class="language-plaintext highlighter-rouge">www.airbnb.com</code> 使用的 TLS 证书是 <code class="language-plaintext highlighter-rouge">*.alipay.com</code>, 而 <code class="language-plaintext highlighter-rouge">airbnb.com</code> 使用的 TLS 证书是 <code class="language-plaintext highlighter-rouge">alipay.com</code></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>www.airbnb.com      =&gt; *.alipay.com
airbnb.com          =&gt; alipay.com
</code></pre></div></div>]]></content><author><name>Gaius</name></author><category term="Devops" /><category term="documentation" /><summary type="html"><![CDATA[概念]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/domain-with-www.jpg" /><media:content medium="image" url="/domain-with-www.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">DNS &amp;amp; CoreDNS</title><link href="/devops/dns-coredns.html" rel="alternate" type="text/html" title="DNS &amp;amp; CoreDNS" /><published>2020-11-05T00:00:00+00:00</published><updated>2020-11-05T00:00:00+00:00</updated><id>/devops/dns-coredns</id><content type="html" xml:base="/devops/dns-coredns.html"><![CDATA[<h2 id="dns">DNS</h2>

<p>DNS（Domain Name System）是一个全球化的分布式数据库，用于存储域名与互联网 IP 地址的映射关系。DNS 分为两大类：权威 DNS，递归 DNS。</p>

<h3 id="权威-dns">权威 DNS</h3>

<p>权威 DNS 是特定域名记录在域名注册商处所设置的 DNS 服务器，用于特定域名本身的管理。且只对自己所拥有的域名进行域名解析，非自己的域名则拒绝访问。</p>

<h3 id="递归-dns">递归 DNS</h3>

<p>递归 DNS 又称 Local DNS，用于域名查询。递归 DNS 会迭代权威服务器返回的应答，直至最终查询到的 IP 地址，将其返回给客户端，并将请求结果缓存到本地。</p>

<h3 id="dns-记录">DNS 记录</h3>

<p>A 记录： 指向 IPv4 地址。</p>

<p>AAAA 记录： 指向 IPv6 地址。</p>

<p>CNAME 记录： 指向域名。</p>

<p>NS 记录： 指定域名解析服务器，即负责管理域名对应 IP 记录服务器。</p>

<p>MX 记录： 指向邮件服务器地址。</p>

<p>TXT 记录： 可任意填写，可为空。</p>

<h3 id="ttl">TTL</h3>

<p>表示解析记录在递归 DNS 中的缓存时间，单位秒。递归 DNS 得到解析记录后，会依照 TTL 进行缓存，但是有可能递归 DNS 不遵循 TTL，比如递归 DNS 觉得 TTL 太小，频繁的递归请求会耗尽递归 DNS 的资源。优化减少请求到权威 DNS 服务器的次数，尽量访问递归 DNS，需要调高 TTL 时长，让客户端尽量访问递归 DNS 缓存。但是 TTL 时长较长可能导致更改源 DNS 记录无效，因为记录值依照 TTL 规范缓存在递归 dns 中。</p>

<h3 id="dns-解析过程">DNS 解析过程</h3>

<p>浏览器输入 www.airbnb.com 地址，则首先根据 Domain 查询 DNS 服务并给出对应 IP。完整的递归 DNS 查询流程需要 DNS 服务器从根域名 “.” 服务器，顶级域名服务器 “.com”，一级域名服务器 “airbnb.com”，一级一级递归查询，直到最终找到权威服务器取得结果，并返回给客户。同时，递归服务器根据域名 TTL，缓存查询结果，便于相同域名重复查询。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwgy1gkfq7ve54wj31300pw42n.jpg" alt="" /></p>

<p>第一次解析时间较长因为有回源权威，第二次解析记录根据 TTL 在递归 DNS 进行缓存。
<img src="https://tva1.sinaimg.cn/large/0081Kckwgy1gkfqjzza6tj30qo03xjsg.jpg" alt="" /></p>

<h2 id="coredns">CoreDNS</h2>

<p>CoreDNS 是一个灵活可扩展的 DNS 服务器，可以作为 Kubernetes 集群 DNS。与 Kubernetes 一样，CoreDNS 项目由 CNCF 托管，其大多数功能都是由插件实现。</p>

<h3 id="dns-in-kubernetes">DNS In Kubernetes</h3>

<blockquote>
  <p>DNS Policy: <a href="https://godoc.org/k8s.io/api/core/v1#DNSPolicy">https://godoc.org/k8s.io/api/core/v1#DNSPolicy</a></p>
</blockquote>

<p>Pod dnsPolicy 为 ClusterFirst，则使用集群 DNS，而非宿主机 DNS 配置。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkfr1so3umj30iu0qdgpb.jpg" alt="" /></p>

<p>查看 Pod 中 DNS 配置文件 /etc/resolv.conf。nameserver 即为 CoreDNS Service 对应 Virtual IP。search 为搜索列表。options ndots 表示大于特定数字，不走 search 按照原域名进行解析，否则会按照 search 列表中逐一匹配查询，如果都是 Not Found 则按照原域名进行解析。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkfquvrxxbj30h5021jrk.jpg" alt="" /></p>

<p>CoreDNS SVC 对应 Virtual IP，即 Pod 中 DNS 配置文件 nameserver 服务器 IP。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkfr6vf7bhj30s404mabf.jpg" alt="" /></p>]]></content><author><name>Gaius</name></author><category term="Devops" /><category term="documentation" /><summary type="html"><![CDATA[DNS]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/dns-coredns.jpg" /><media:content medium="image" url="/dns-coredns.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Tcpdump &amp;amp; Wireshark</title><link href="/devops/tcpdump-wireshark.html" rel="alternate" type="text/html" title="Tcpdump &amp;amp; Wireshark" /><published>2018-12-23T00:00:00+00:00</published><updated>2018-12-23T00:00:00+00:00</updated><id>/devops/tcpdump-wireshark</id><content type="html" xml:base="/devops/tcpdump-wireshark.html"><![CDATA[<h2 id="tcp-标志位tcp-flags">TCP 标志位(TCP Flags)</h2>

<blockquote>
  <p>Transmission Control Protocol: <a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol">https://en.wikipedia.org/wiki/Transmission_Control_Protocol</a></p>
</blockquote>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwgy1gkelu9itx8j30vc0dgjtn.jpg" alt="" /></p>

<p>TCP 首部中 TCP Flags 位于第 14 个字节, 包含 8 个比特位。主要用作操控 TCP 状态机, 8 个比特位都表示特定功能, 分别为:</p>

<p>CWR &amp; ECE: 用来配合做 congestion control，一般情况下和应用层关系不大。发送方的包 ECE 为 0 时，表示出现了 congestion。接收方回的包里 CWR 为 1 时，表示收到 congestion 信息并做了处理。</p>

<p>URG: 表示 TCP 包的紧急指针域有效。用来保证 TCP 连接不被中断，并且督促中间层设备要尽快处理这些数据。</p>

<p>ACK: 表示应答域有效。有两个取值 0 和 1，为 1 的时候表示应答域有效，反之为 0。</p>

<p>PSH: 表示 Push 操作。所谓 Push 操作就是指在数据包到达接收端以后，立即传送给应用程序， 而不是在缓冲区中排队。</p>

<p>RST: 表示连接复位请求。用来复位那些产生错误的连接，也被用来拒绝错误和非法的数据包。</p>

<p>SYN: 表示同步序号，用来建立连接。SYN 标志位和 ACK 标志位搭配使用，当连接请求的时候 SYN=1，ACK=0。连接被响应的时候 SYN=1，ACK=1。</p>

<p>FIN: 表示发送端已经达到数据末尾。也就是说双方的数据传送完成，没有数据可以传送了，发送 FIN 标志位的 TCP 数据包后，连接将被断开。</p>

<h2 id="连接复位rst">连接复位(RST)</h2>

<p>TCP Flags 用 RST 表示复位，用来表示异常关闭连接。发送 RST 包关闭连接时，不必等缓冲区的包都发出去，直接就丢弃缓存区的包发送 RST 包。而接收端收到 RST 包后，也不必发送 ACK 包来确认。主要有以下四种情况可能导致 RST:</p>

<ul>
  <li>端口未打开</li>
  <li>Request 请求超时</li>
  <li>Socket 提前关闭</li>
  <li>已经关闭 Socket 上收到数据</li>
</ul>

<h2 id="线上排查过程">线上排查过程</h2>

<p>排查请求异常断连, 应用日志显示为 Read ECONNRESET 其实日志内容已经告诉了问题原因, ECONNRESET 即收到了 TCP RST 报文。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: <span class="nb">read </span>ECONNRESET at TCP.onStreamRead <span class="o">(</span>internal/stream_base_commons.js:200:27<span class="o">)</span> <span class="o">{</span>
  errno: <span class="s1">'ECONNRESET'</span>,
  code: <span class="s1">'ECONNRESET'</span>,
  syscall: <span class="s1">'read'</span>,
  name: <span class="s1">'ResponseError'</span>,
  data: undefined,
  path: <span class="s1">'/mgw.htm'</span>,
  status: <span class="nt">-1</span>,
  headers: <span class="o">{}</span>,
  res: <span class="o">{</span>
    status: <span class="nt">-1</span>,
    statusCode: <span class="nt">-1</span>,
    statusMessage: null,
    headers: <span class="o">{}</span>,
    size: 0,
    aborted: <span class="nb">false</span>,
    rt: 90360,
    keepAliveSocket: <span class="nb">false</span>,
    data: undefined,
    requestUrls: <span class="o">[</span> <span class="s1">'http://google.com'</span> <span class="o">]</span>,
    timing: null,
    remoteAddress: <span class="s1">'108.177.8.139'</span>,
    remotePort: 80,
    socketHandledRequests: 1,
    socketHandledResponses: 0
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>登陆 CentOS or Fedora 服务器安装 tcpdump，正常情况 Linux &amp; macOS 默认安装。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>yum <span class="nb">install </span>tcpdump
</code></pre></div></div>

<p>服务器内用 tcpdump 抓包, 数据倒入 out.pcap 文件内。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcpdump <span class="nt">-i</span> any <span class="nt">-w</span> out.pcap
</code></pre></div></div>

<p>或者根据 IP 进行抓包。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcpdump <span class="nt">-i</span> any host 8.8.8.8 <span class="nt">-w</span> out.pcap
</code></pre></div></div>

<p>out.pcap 文件导出, 权限限制情况下 scp 不可取。所以用 OSS 当媒介上传, 安装阿里云 OSS Client。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://gosspublic.alicdn.com/ossutil/1.6.18/ossutil64
<span class="nb">chmod </span>755 ossutil64
</code></pre></div></div>

<p>生成 OSS 相关配置文件。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./ossutil64 config <span class="nt">-e</span> oss-ap-southeast-1.aliyuncs.com <span class="nt">-i</span> ak-id <span class="nt">-k</span> ak-secret  <span class="nt">-L</span> CH <span class="nt">-c</span> ./myconfig
</code></pre></div></div>

<p>上传 .pcap 文件至 OSS。</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./ossutil64 <span class="nb">cp </span>out.pcap oss://oss-ap-southeast-1.aliyuncs.com/out.pcap <span class="nt">--config-file</span> ./myconfig
</code></pre></div></div>

<p>out.pcap 在 Wireshark 中打开进行分析。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkemwv7qf7j31d90u0npf.jpg" alt="" /></p>

<p>通过 tcp.flags.reset == 1 过滤出 RST 异常数据包。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkemyjprk4j31dh0u0446.jpg" alt="" /></p>

<p>根据 IP 以及 Port 端口号 ip.src eq 121.0.26.95 or ip.dst eq 121.0.26.95 and tcp.port == 49688 过滤出，整体 TCP 传输报文。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken04741vj324g0lodu9.jpg" alt="" /></p>

<p>数据报文进行分析, 首先进行 TCP 三次握手。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken1bbkytj324k0lc7j0.jpg" alt="" /></p>

<p>报文解析客户端到服务端数据报文, 2047 为帧号。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken7kxu1vj31d90u0npd.jpg" alt="" /></p>

<p>2048 帧，服务端的返回报文，每个报文都有对应的响应报文。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken392gbkj31d90u0gso.jpg" alt="" /></p>

<p>2057 帧后客户端 3s 没有返回，服务端超时，发送 RST 报文中止了链接。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken40n91qj31d90u0e81.jpg" alt="" />
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken3y5k8mj31d90u0npd.jpg" alt="" /></p>

<p>原因为服务端发送 RST 报文中止请求导致。正常情况下中止是通过服务端发送 FIN 四次挥手结束的。
<img src="https://tva1.sinaimg.cn/large/0081Kckwly1gken6d62wwj31df0u0180.jpg" alt="" /></p>]]></content><author><name>Gaius</name></author><category term="Devops" /><category term="documentation" /><summary type="html"><![CDATA[TCP 标志位(TCP Flags)]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/tcpdump-wireshark.png" /><media:content medium="image" url="/tcpdump-wireshark.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SSL 证书实践</title><link href="/devops/ssl-certificate.html" rel="alternate" type="text/html" title="SSL 证书实践" /><published>2018-09-11T00:00:00+00:00</published><updated>2018-09-11T00:00:00+00:00</updated><id>/devops/ssl-certificate</id><content type="html" xml:base="/devops/ssl-certificate.html"><![CDATA[<p>SSL 证书实践过程中，避免不了遇到各种问题，本文主要介绍实践过程中相关经验。</p>

<h2 id="概念">概念</h2>

<h2 id="ssltls--https">SSL、TLS &amp; HTTPS</h2>

<p>SSL: 指安全套接字层，简而言之，它是一项标准技术，可确保互联网连接安全，保护两个系统之间发送的任何敏感数据。</p>

<p>TLS: 传输层安全是更为安全的升级版 SSL。</p>

<p>HTTPS: 基于 TLS/SSL 的安全套接字上的的应用层协议，除了传输层进行了加密外，其它与常规 HTTP 协议基本保持一致。</p>

<h2 id="tls-版本">TLS 版本</h2>

<p>可以通过 <a href="https://www.ssllabs.com/ssltest/analyze.html">ssl-labs</a> 来检查, 域名对应 TLS 版本信息。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkeexgqk82j31gc0dgta4.jpg" alt="" /></p>

<p>NGINX: 配置 TLS 版本参考 <a href="http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols">ssl-protocols</a>。</p>

<p>NGINX Ingress: 默认使用兼容性好且安全性高的 TLS v1.2, 配置具体版本参考 <a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#ssl-protocols">configmap-ssl-protocols</a>。</p>

<h2 id="证书">证书</h2>

<h3 id="证书链分级">证书链分级</h3>

<p>完整的证书内容一般分为 3 级，服务端证书-中间证书-根证书，即 End-user Certificates， Intermediates Certificates 和 Root Certificates。</p>

<ul>
  <li>End-user Certificates：用来加密传输数据的公钥的证书，是 https 中使用的证书。开发者把证书部署在 marmot-cloud.com 服务器上。</li>
  <li>Intermediates Certificates：CA 用来认证公钥持有者身份的证书，即确认 https 使用的 end-user 证书是属于 marmot-cloud.com 的证书。</li>
  <li>Root Certificates：用来认证 intermediates 证书是合法证书的证书。</li>
</ul>

<h3 id="证书链修复">证书链修复</h3>

<p>证书链不完整常会报错如下:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>the certificate is not trusted in all web browsers. you may need to install an intermediate/chain certificate to link it to a trusted root certificate.
</code></pre></div></div>

<p>引起该问题原因有很多, 比如老版本客户端系统 Root Certificates 未默认集成导致证书链不全。可以通过提供 End-user Certificates 基于 <a href="https://myssl.com/chain_download.html">my-ssl</a> 来修复完成证书链。</p>

<h3 id="证书链查询">证书链查询</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl s_client <span class="nt">-servername</span> airbnb.com <span class="nt">-connect</span> airbnb.com:443 <span class="nt">-showcerts</span>
</code></pre></div></div>

<p>查询结果显示, 0 级为 End-user Certificates(airbnb.com), 1 级为 Intermediates Certificates, 2 级为 Root Certificates。</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONNECTED(00000006)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert SHA2 Secure Server CA
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "Airbnb, Inc.", CN = airbnb.com
verify return:1
---
Certificate chain
 0 s:/C=US/ST=California/L=San Francisco/O=Airbnb, Inc./CN=airbnb.com
   i:/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
-----BEGIN CERTIFICATE-----
MIIIvjCCB6agAwIBAgIQA/y0YGMRY6a9g87hGSCsxzANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMjAwNjA1MDAwMDAwWhcN
MjIwMjA3MTIwMDAwWjBmMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p
YTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMMQWlyYm5iLCBJbmMu
MRMwEQYDVQQDEwphaXJibmIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAysTmtbp49OE4RrOiV8ctfB8moVQI99IOKIuLojnv55lt2kKDSkSxhZX1
zgDZtMKN6ziYjz2+pkqpEtdsbuNxcFaH9Aadc2bKH6OnuXkVoGSvPGKIv+aR7Wu6
Ylg01W6liBF0+PGEVb9Ea9gbKnzc+8gy6RVpQ+e19/NVBui6S0F9k290B/GdCjGZ
Yk7f6rZCLCBEQ6gPsLtXroIzAX+1fKyiUOHf3ljzAMqPfyyPwVmDMXKGhXDcKopR
qccsdEjPEiOsVpPVRX2SKdyou2D95HSTQsErOGhJEbU+ZvNaBqeubUerIxz0/07n
0mShWRNQ/jSnSim0Ba6VW2NoOsvkqwIDAQABo4IFfzCCBXswHwYDVR0jBBgwFoAU
D4BhHIIxYdUvKOeNRji0LOHG2eIwHQYDVR0OBBYEFN2jfgK3oODTwv+vLe8jRshe
+KhbMIICPgYDVR0RBIICNTCCAjGCCmFpcmJuYi5jb22CDWFpcmJuYi50cmF2ZWyC
CWFpcmJuYi5ka4IJYWlyYm5iLmZygglhaXJibmIuZmmCCWFpcmJuYi5kZYIJYWly
Ym5iLm14ggphYmIudHJhdmVsggphaXJibmIuY2F0gglhaXJibmIuanCCCWFpcmJu
Yi5pdIIJYWlyYm5iLmlzgglhaXJibmIucGyCCWFpcmJuYi5ncoIKYWlyYm5iLm9y
Z4IIdmFtby5jb22CCWFpcmJuYi5ydYIJYWlyYm5iLmJlgglhaXJibmIubmyCCWFp
cmJuYi5hdIIJYWlyYm5iLnB0gglhaXJibmIuY2iCCWFpcmJuYi5pZYIJYWlyYm5i
LmNhgglhaXJibmIuc2WCCWFpcmJuYi5jeoINYWNjb21hYmxlLmNvbYIJYWlyYm5i
Lm5vgglhaXJibmIuZXOCCWFpcmJuYi5odYINYWlyYm5iLmNvbS5zZ4IMYWlyYm5i
LmNvLm56ggxhaXJibmIuY28udWuCDWFpcmJuYi5jb20udm6CDGFpcmJuYi5jby5p
ZIINYWlyYm5iLmNvbS51YYINYWlyYm5iLmNvbS5icoIMYWlyYm5iLmNvLmtygg1h
aXJibmIuY29tLm15gg1haXJibmIuY29tLmF1gg1haXJibmIuY29tLmhrgg1haXJi
bmIuY29tLnR3gg1haXJibmIuY29tLmhygg1haXJibmIuY29tLnRyggxhaXJibmIu
Y28uaWwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjBrBgNVHR8EZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
c3NjYS1zaGEyLWc2LmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29t
L3NzY2Etc2hhMi1nNi5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAQEwKjAoBggr
BgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBAgIw
fAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
dC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E
aWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADCCAX8G
CisGAQQB1nkCBAIEggFvBIIBawFpAHYAKXm+8J45OSHwVnOfY6V35b5XfZxgCvj5
TV0mXCVdx4QAAAFyhhhGAQAABAMARzBFAiEAupludhrxIbp4FBRTl3Wxh/688g2a
FcE6TMZ6D1zlCFICIBTi/C5AIKjfM7hosedQgwScfNgIzluEZy/DOBlzvs0yAHcA
QcjKsd8iRkoQxqE6CUKHXk4xixsD6+tLx2jwkGKWBvYAAAFyhhhFugAABAMASDBG
AiEAgP1ZY3UMrFvWVKP5XhMhfXQefbb5KCnfyJCWaISd9dkCIQC3nddMiCmn9c7e
4rwAFASvJUDhqeWOeP20HnT1XCQ3qAB2AEalVet1+pEgMLWiiWn0830RLEF0vv1J
uIWr8vxw/m1HAAABcoYYRkcAAAQDAEcwRQIgfdUJZTJCxSl87rZWarSqLTZwSWCU
ucRNiC7MYGXvHL8CIQCGt12tJL1FYwn3WqjxoW7G9SuqQrHGNg/xcWgiURPHfjAN
BgkqhkiG9w0BAQsFAAOCAQEAi81YDzYL1LNb1TPkqRYFMvFrKT5RFADKNwjXg/vD
xHKVlshhTQ2aXqy70dry0nBUCQWDdLUKJBkA8tG3uRq9v3X1FDZjR2x+lDcCpdm3
y5b1RDq8l57hl7ChbeTfi4/QpE7M2ncmcX+em+sq3osS12GswCZVURxEk5cKRn/z
Pn5BTz09o/Nse277Noxk0T3vNH1meXbYlBQ6vXc/qWQWzO6NtlehuAhPZgHypqqg
4jkHJxlzWZIjQZdP9afqpWwbenvCfzjnlSDmUBfwOXtzBzwKepkO0j3/bDO5ayD1
y5vqxhMovE/t+KPT1Rp3F1+982HSCQ7wuoCftlfxdesYJg==
-----END CERTIFICATE-----
 1 s:/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
-----BEGIN CERTIFICATE-----
MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg
U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83
nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd
KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f
/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX
kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0
/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C
AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY
aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6
Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1
oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD
QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh
xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB
CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl
5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA
8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC
2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit
c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0
j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz
-----END CERTIFICATE-----
 2 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
   i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
---
Server certificate
subject=/C=US/ST=California/L=San Francisco/O=Airbnb, Inc./CN=airbnb.com
issuer=/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
---
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5061 bytes and written 345 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384
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-AES256-GCM-SHA384
    Session-ID: D2F505134A8189DB0A1C39B305819070F69E37E44809EDDC4811CA376EA83310
    Session-ID-ctx:
    Master-Key: BE70C8F66DD5FB76BF2F880D080A3F773012BA1C285E98D72C333AE38CF4F204EB58A176B62E7246B7610B466AD93BAC
    TLS session ticket lifetime hint: 600 (seconds)
    TLS session ticket:
    0000 - fb 9b 78 0b 79 d5 60 9a-6f a7 5e c4 ca b7 5f 1f   ..x.y.`.o.^..._.
    0010 - 3a ce 51 5a 0a 79 1d 54-cc a6 e6 3f eb ee 90 d0   :.QZ.y.T...?....
    0020 - cb e8 91 43 f7 74 40 0a-a0 82 cc 50 f1 50 89 46   ...C.t@....P.P.F
    0030 - 98 f1 89 f9 23 dd 6f e7-07 79 cd d2 a0 cb 14 33   ....#.o..y.....3
    0040 - 35 60 ae 1c 0b 24 0e 80-40 ab 03 41 5f a3 0b a4   5`...$..@..A_...
    0050 - 2a 1f 36 59 a0 eb 4e 18-1d ea 9c 50 c9 08 4b 44   *.6Y..N....P..KD
    0060 - 42 0f da 5a 1b 4b ec b4-43 e5 cb 44 b9 c2 24 02   B..Z.K..C..D..$.
    0070 - 25 b0 0f c8 02 63 89 48-b7 9c a8 a5 2b 55 92 cd   %....c.H....+U..
    0080 - 87 9f 02 59 26 46 b6 e4-e7 17 4d c7 6f 60 78 28   ...Y&amp;F....M.o`x(
    0090 - cf 5a 47 aa fc db 8e bd-5c 30 76 8d 3b 48 7b 64   .ZG.....\0v.;H{d
    00a0 - 62 56 7d 35 c0 6a 52 2c-d1 a4 48 36 67 37 2d 39   bV}5.jR,..H6g7-9
    00b0 - ad 8c d6 cf 2c d6 00 23-c0 22 91 36 a5 12 84 6a   ....,..#.".6...j

    Start Time: 1604568329
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
read:errno=0
</code></pre></div></div>

<h3 id="查看证书支持域名">查看证书支持域名</h3>

<p>可以通过浏览器查看当前域名所持有证书, 支持的域名列表。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwgy1gkefi8tt8uj30qw0py41o.jpg" alt="" /></p>

<h2 id="其他相关知识">其他相关知识</h2>

<h3 id="hsts">HSTS</h3>

<p><a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security">HSTS</a> 是一套由互联网工程任务组发布的互联网安全策略机制。由于该机制的存在，证书如果配置错误可能对用户造成极大损害，因为 HSTS 缓存是需要手动清除。</p>

<h4 id="响应头格式">响应头格式</h4>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Strict-Transport-Security: max-age=expireTime [; includeSubDomains] [; preload]
</code></pre></div></div>

<h4 id="处理方式">处理方式</h4>

<p>你的网站第一次通过 HTTPS 请求，服务器响应 Strict-Transport-Security 头，浏览器记录下这些信息，然后后面尝试访问这个网站的请求都会自动把 HTTP 替换为 HTTPS。</p>

<p>当 HSTS 头设置的过期时间到了，后面通过 HTTP 的访问恢复到正常模式，不会再自动跳转到 HTTPS。</p>

<p>每次浏览器接收到 Strict-Transport-Security 头，它都会更新这个网站的过期时间，所以网站可以刷新这些信息，防止过期发生。</p>

<p>Chrome、Firefox 等浏览器里，当您尝试访问该域名下的内容时，会产生一个 307 Internal Redirect（内部跳转），自动跳转到 HTTPS 请求。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkegum13f3j327w0hm0zm.jpg" alt="" /></p>

<h4 id="浏览器功能">浏览器功能</h4>

<p>Chrome 浏览器内可以通过访问 chrome://net-internals/#hsts 链接，来查询域名对应 HSTS 设置。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkeg6opnjnj327n0u044i.jpg" alt="" /></p>

<p>当然也可以手动清除浏览器本地 HSTS 缓存。</p>

<p><img src="https://tva1.sinaimg.cn/large/0081Kckwly1gkeg8ltl3kj31ke0u0al1.jpg" alt="" /></p>

<h3 id="pgp-加密证书私钥">PGP 加密证书私钥</h3>

<p>一般情况下 SSL 证书私钥发放通过 Email 等方式, 传输过程中需要加密传输, 常见加密方式为 PGP。</p>

<h4 id="pgp">PGP</h4>

<p><a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy">PGP</a> 加密程序通过一系列密码技术，为数据通讯提供安全隐私、认证。PGP 软件一般遵循 <a href="https://tools.ietf.org/html/rfc4880">OpenPGP 标准</a>, 可用于：</p>

<ul>
  <li>数字签名</li>
  <li>加密文本、电邮、文件目录、整盘加密</li>
</ul>

<h4 id="macos-使用-pgp">macOS 使用 PGP</h4>

<blockquote>
  <p>PGP Suite: <a href="https://gpgtools.org">https://gpgtools.org</a></p>
</blockquote>

<p>下载安装 PGP 软件包后进入 Terminal。</p>

<p>生成密钥对:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--gen-key</span>
</code></pre></div></div>

<p>导入其他公钥:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--import</span> pub.asc
</code></pre></div></div>

<p>导出本地公钥:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">--export</span> keyId <span class="o">&gt;</span> pub.asc
</code></pre></div></div>

<p>文件加密:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">-e</span> <span class="nt">-a</span> <span class="nt">-r</span> keyId filename
</code></pre></div></div>

<p>文件解密:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg <span class="nt">-d</span> filename
</code></pre></div></div>

<p>查看 PGP 公钥/文件的属性信息:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gpg filename
</code></pre></div></div>]]></content><author><name>Gaius</name></author><category term="Devops" /><category term="documentation" /><summary type="html"><![CDATA[SSL 证书实践过程中，避免不了遇到各种问题，本文主要介绍实践过程中相关经验。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/ssl-certificate.png" /><media:content medium="image" url="/ssl-certificate.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>