為什么選擇微服務(wù)?
如果你正在開發(fā)一個(gè)大型/復(fù)雜的應(yīng)用,并且你經(jīng)常需要快速,可靠地升級部署 ,那么微服架構(gòu)是一個(gè)不錯(cuò)的選擇。
但是如何提高微服務(wù)架構(gòu)的安全性呢?
1.通過設(shè)計(jì)確保安全
設(shè)計(jì)安全,意味著從一開始就應(yīng)該將安全性納入軟件設(shè)計(jì)中。關(guān)于安全,其中最常見的一個(gè)威脅就是惡意字符。
我問我的朋友羅伯·溫奇(Rob Winch)他對刪除惡意字符的想法。Rob是Spring Security項(xiàng)目的負(fù)責(zé)人,被廣泛認(rèn)為是安全專家。
我認(rèn)為從一開始就將代碼設(shè)計(jì)為安全是有意義的。但是,完全刪除惡意字符不可能的。
什么是惡意字符,實(shí)際上取決于它所使用的上下文。只是要找出是否存在其他注入
攻擊(即JAVAScript,SQL等),你就可以確保html上下文中沒有惡意字符。需要注意的是,HTML文檔的編碼也是基于上下文的。
限制字符也不總是可行的。在許多情況下,軟件認(rèn)為是惡意的字符,但這在某人的名字中是完全有效的字符。那應(yīng)該怎么辦?
我覺得,最好在使用字符的上下文中判斷,而不是嘗試限制字符。
—羅伯·溫奇
作為工程師,我們很早就明白了–創(chuàng)建精心設(shè)計(jì)的軟件體系結(jié)構(gòu)的重要性。軟件開發(fā)中常見的安全威脅,促使組織在系統(tǒng)架構(gòu)時(shí)要時(shí)刻考慮軟件的安全性。系統(tǒng)要能夠在受到攻擊時(shí),也要有用于執(zhí)行必要的身份驗(yàn)證,授權(quán),數(shù)據(jù)加密,數(shù)據(jù)完整性和可用性的解決方案。
從InfoQ文章分析中,我們可以看到:OWASP Top 10在過去十年中并沒有發(fā)生太大變化。SQL注入仍然是最常見的攻擊。十年來,我們?nèi)栽诶^續(xù)重復(fù)同樣的錯(cuò)誤。— Johnny Xmas
這就是為什么需要將安全預(yù)防措施納入軟件架構(gòu)的原因。
OWASP
開源的Web應(yīng)用程序安全項(xiàng)目(Open Web Application Security Project ,OWASP)是一個(gè)非營利性基金會(huì),致力于改善軟件的安全性。他們向開發(fā)人員和技術(shù)人員提供:
- 工具和資源
- 社區(qū)與網(wǎng)絡(luò)
- 教育培訓(xùn)
我喜歡Dan Bergh Johnsson,Daniel Deogun和Daniel Sawano撰寫的《Secure by Design》一書中的示例。它們展示了如何開發(fā)一個(gè)基本User實(shí)體對象,并且該對象需要在web頁面上顯示用戶名。
public class User {
private final Long id;
private final String username;
public User(final Long id, final String username) {
this.id = id;
this.username = username;
}
// ...
}
如果你接受用戶名的任何字符串值,則有人可以使用用戶名執(zhí)行XSS攻擊。你可以使用輸入校驗(yàn)來解決此問題,如下所示。
import static com.example.xss.ValidationUtils.validateForXSS;
import static org.Apache.commons.lang3.Validate.notNull;
public class User {
private final Long id;
private final String username;
public User(final Long id, final String username) {
notNull(id);
notNull(username);
this.id = notNull(id);
this.username = validateForXSS(username);
}
}
但是,此代碼仍然有問題。
- 開發(fā)人員需要考慮安全漏洞
- 開發(fā)人員必須是安全專家并且知道使用 validateForXSS()
- 它假設(shè)編寫代碼的人可以想到現(xiàn)在或?qū)砜赡馨l(fā)生的每一個(gè)潛在弱點(diǎn)
更好的設(shè)計(jì)是創(chuàng)建一個(gè)Username封裝所有安全問題的類。
import static org.apache.commons.lang3.Validate.*;
public class Username {
private static final int MINIMUM_LENGTH = 4;
private static final int MAXIMUM_LENGTH = 40;
private static final String VALID_CHARACTERS = "[A-Za-z0-9_-]+";
private final String value;
public Username(final String value) {
notBlank(value);
final String trimmed = value.trim();
inclusiveBetween(MINIMUM_LENGTH,
MAXIMUM_LENGTH,
trimmed.length());
matchesPattern(trimmed,
VALID_CHARACTERS,
"Allowed characters are: %s", VALID_CHARACTERS);
this.value = trimmed;
}
public String value() {
return value;
}
}
public class User {
private final Long id;
private final Username username;
public User(final Long id, final Username username) {
this.id = notNull(id);
this.username = notNull(username);
}
這樣,你的設(shè)計(jì)使開發(fā)人員更容易編寫安全代碼。
2.掃描依賴
我們用于開發(fā)軟件的許多類庫,很多都依賴于第三方類庫,傳遞性依賴性有時(shí)會(huì)產(chǎn)生大量的依賴鏈,其中一些可能就有安全漏洞。
你可以在代碼存儲(chǔ)庫上,使用掃描程序來識(shí)別易受攻擊的依賴項(xiàng)。你也應(yīng)該在部署的流水線,主要代碼行,發(fā)布的代碼版本和新的代碼貢獻(xiàn)中掃描漏洞。
Snyk調(diào)查:25%的項(xiàng)目未報(bào)告安全問題;多數(shù)只添加發(fā)行說明;只有10%的人報(bào)告CVE。
如果你是GitHub用戶,則可以使用dependabot通過pull請求提供自動(dòng)更新。GitHub還可以在存儲(chǔ)庫中啟用安全警報(bào)。
你還可以使用功能更全的解決方案,例如Snyk和JFrog Xray。
3.隨處使用HTTPS
你應(yīng)該在所有地方都使用HTTPS,即使對于靜態(tài)站點(diǎn)也要如此。如果你有HTTP連接,請將其更改為HTTPS,確保工作流程的各個(gè)方面(從Maven存儲(chǔ)庫到 XSDs )都使用HTTPS URI。
HTTPS的正式名稱是:傳輸層安全性(又名TLS)。它旨在確保計(jì)算機(jī)應(yīng)用程序之間的隱私和數(shù)據(jù)完整性。 How HTTPS Works 是一個(gè)很好的網(wǎng)站,可用于學(xué)習(xí)有關(guān)HTTPS的更多信息。
要使用HTTPS,你需要一個(gè)證書。它具有兩個(gè)重要的作用, 建立信息安全通道,保障隱私數(shù)據(jù)安全 ,并且還驗(yàn)證網(wǎng)站的真實(shí)性,防止誤入釣魚網(wǎng)站 。
Let’s Encrypt 提供免費(fèi)證書,你可以使用其API自動(dòng)續(xù)訂它們。
Let’s Encrypt于2016年4月12日啟動(dòng),該組織宣布自成立以來已總共發(fā)行了10億張
證書,并且據(jù)估計(jì),Let’s Encrypt使Internet上安全網(wǎng)站的百分比增加了一倍。
Let’s Encrypt建議你使用Certbot來獲取和更新證書。Certbot是一個(gè)免費(fèi)的開源軟件工具,其中Certbot網(wǎng)站可以讓你選擇你的Web服務(wù)器和系統(tǒng),然后自動(dòng)生成證書。例如,Ubuntu使用Nginx的的說明。
要在Spring Boot中使用證書,你只需要在src/main/resources/application.yml進(jìn)行一些配置。
server:
ssl:
key-store: classpath:keystore.p12
key-store-password: password
key-store-type: pkcs12
key-alias: Tomcat
key-password: password
port: 8443
在配置文件中存儲(chǔ)密碼和密鑰,有很大的安全風(fēng)險(xiǎn)。我將在下面顯示如何加密密鑰。
你可能還想強(qiáng)制使用HTTPS。你可以在我以前的博客文章“ 保護(hù)Spring Boot應(yīng)用程序的10種出色方法”中看到如何做。通常,強(qiáng)制使用HTTPS,需要使用HTTP Strict-Transport-Security響應(yīng)頭(縮寫為HSTS)來告訴瀏覽器它們只能使用HTTPS訪問網(wǎng)站。
要了解基于Spring的微服務(wù)如何使用HTTPS,請參閱使用HTTPS和OAuth 2.0保護(hù)Spring微服務(wù)。
安全的GraphQL API
GraphQL 既是一種用于 API 的查詢語言也是一個(gè)滿足你數(shù)據(jù)查詢的運(yùn)行時(shí)。 GraphQL 對你的 API 中的數(shù)據(jù)提供了一套易于理解的完整描述,使得客戶端能夠準(zhǔn)確地獲得它需要的數(shù)據(jù),而且沒有任何冗余,也讓 API 更容易地隨著時(shí)間推移而演進(jìn),還能用于構(gòu)建強(qiáng)大的開發(fā)者工具。
如果你想請求具備OAuth 2.0和React功能的GraphQL服務(wù)器,則只需傳遞一個(gè)Authorization標(biāo)頭即可。
Apollo是一個(gè)用于構(gòu)建數(shù)據(jù)圖表的平臺(tái),Apollo Client具有React和Angular的功能。
const clientParam = { uri: '/graphql' };
const myAuth = this.props && this.props.auth;
if (myAuth) {
clientParam.request = async (operation) => {
const token = await myAuth.getAccessToken();
operation.setContext({ headers: { authorization: token ? `Bearer ${token}` : '' } });
}
}
const client = new ApolloClient(clientParam);
Angular配置安全的Apollo Client
export function createApollo(httpLink: HttpLink, oktaAuth: OktaAuthService) {
const http = httpLink.create({ uri });
const auth = setContext((_, { headers }) => {
return oktaAuth.getAccessToken().then(token => {
return token ? { headers: { Authorization: `Bearer ${token}` } } : {};
});
});
return {
link: auth.concat(http),
cache: new InMemoryCache()
};
}
在服務(wù)器上,你可以使用任何用于保護(hù)REST API端點(diǎn)的安全來保護(hù)GraphQL。
安全的RSocket端點(diǎn)
RSocket是用于構(gòu)建云原生和微服務(wù)應(yīng)用程序的下一代的響應(yīng)式的第5層應(yīng)用程序通信協(xié)議。
這是什么意思?這意味著RSocket具有內(nèi)置的響應(yīng)式語義,因此它可以與客戶端可靠地通信。RSocket網(wǎng)站介紹,它可應(yīng)用于 Java,JavaScript,Go, .NET, C++, 和Kotlin中。
Spring Security 5.3.0完全支持RSocket應(yīng)用程序。
要了解有關(guān)RSocket的更多信息,我建議閱讀RSocket入門:Spring Boot Server。
4.使用身份令牌
OAuth 2.0自2012年以來就提供了委托授權(quán)。2014年,OpenIDConnect在的OAuth 2.0之上添加了聯(lián)合身份。它們共同提供了一個(gè)標(biāo)準(zhǔn)規(guī)范,你可以據(jù)此編寫代碼,并可以在 IdPs (Identity Providers) 中使用。
該規(guī)范,還允許你通過向/userinfo端點(diǎn)發(fā)送訪問令牌來查找用戶的身份。你可以使用OIDC發(fā)現(xiàn)來查找此端點(diǎn)的URI,這提供了一種獲取用戶身份的標(biāo)準(zhǔn)方法。
如果要在微服務(wù)之間進(jìn)行通信,則可以使用OAuth 2.0的客戶端憑據(jù)流來實(shí)現(xiàn)安全的服務(wù)器到服務(wù)器通信。在下圖中,API Client是一臺(tái)服務(wù)器,而API Server另一臺(tái)服務(wù)器。
授權(quán)服務(wù)器:多對一還是一對一?
如果你使用OAuth 2.0保護(hù)服務(wù)安全,使用的還是授權(quán)服務(wù)器。典型的設(shè)置是多對一關(guān)系,在這種關(guān)系中,你有許多微服務(wù)與授權(quán)服務(wù)器通信。
這種方法的優(yōu)點(diǎn):
- 服務(wù)可以使用訪問令牌與任何其他內(nèi)部服務(wù)進(jìn)行對話(因?yàn)樗鼈兌际沁B接到同一個(gè)授權(quán)服務(wù)器)
- 有了一個(gè)可以查找所有范圍和權(quán)限定義的地方
- 開發(fā)人員和安全人員更易于管理
- 交互更快
缺點(diǎn):
- 如果一項(xiàng)服務(wù)的令牌遭到破壞,則所有服務(wù)都將面臨風(fēng)險(xiǎn)
- 安全邊界模糊
另一種更安全的替代方法是一對一方法,其中每個(gè)微服務(wù)都綁定到其自己的授權(quán)服務(wù)器。如果他們需要相互通信,則需要在信任之前進(jìn)行注冊。
這種體系結(jié)構(gòu)使你可以明確定義安全邊界。但是,它比較慢,也難于管理。
我的建議:使用多對一關(guān)系,直到你有計(jì)劃和文檔來支持一對一關(guān)系為止。
在JWT上使用PASETO令牌
在過去的幾年中, JSON Web Tokens (JWT) 變得非常流行,但也遭到了抨擊。主要是因?yàn)樵S多開發(fā)人員嘗試使用JWT,來避免會(huì)話的服務(wù)器端存儲(chǔ)。請參閱為什么不建議使用JWT。
我的同事Randall Degges和Brian Demers在PASETO( platform-agnostic security tokens)上寫了一些有益的文章。
- 全面了解PASETO
- 用Java創(chuàng)建和驗(yàn)證PASETO令牌
長話短說:使用PASETO令牌并不像聽起來那么容易。如果你想編寫自己的安全性,則可以使用它。但是,如果你要使用知名的云提供商,則很可能它還不支持PASETO標(biāo)準(zhǔn)。
5.加密和保護(hù)密鑰
當(dāng)你開發(fā)與授權(quán)服務(wù)器或其他服務(wù)通信的微服務(wù)時(shí),這些微服務(wù)可能會(huì)存儲(chǔ)用于通信的密鑰。這些密鑰可能是API密鑰,客戶密鑰或用于基本身份驗(yàn)證的憑據(jù)。
要更安全地使用密鑰,第一步是將其存儲(chǔ)在環(huán)境變量中。但這只是開始,你應(yīng)該盡力加密你的密鑰。
在Java世界中,我最熟悉HashiCorp Vault和Spring Vault。
下圖展示的是Amazon KMS是如何工作。
簡而言之,它的工作方式是:
- 使用KMS生成主密鑰
- 每次你想要加密數(shù)據(jù)時(shí),你都要求AWS 為你生成一個(gè)新的數(shù)據(jù)密鑰。
- 然后,你可以使用數(shù)據(jù)密鑰對數(shù)據(jù)進(jìn)行加密
- 然后,Amazon將使用主密鑰對你的數(shù)據(jù)密鑰進(jìn)行加密
- 然后,你將合并加密的數(shù)據(jù)密鑰和加密的數(shù)據(jù)以創(chuàng)建加密的消息。該加密的消息是你的最終輸出,你就可以將它存儲(chǔ)在文件或數(shù)據(jù)庫中。
這樣,你就無需擔(dān)心保護(hù)密鑰的安全性-密鑰始終是唯一且安全的。你還可以使用Azure KeyVault來存儲(chǔ)你的密鑰。
6.通過交付流水線驗(yàn)證安全性
依賴關(guān)系和容器掃描,從源頭保障了程序的安全,但是在執(zhí)行CI(持續(xù)集成)和CD(持續(xù)部署)流水線時(shí),還應(yīng)該執(zhí)行測試。
Atlassian有篇文章,DevSecOps:將安全性注入CD流水線,建議使用安全性單元測試,靜態(tài)分析安全性測試(SAST)和動(dòng)態(tài)分析安全性測試(DAST)。
你的代碼交付流水線可以自動(dòng)執(zhí)行這些安全檢查,但是可能會(huì)花費(fèi)一些時(shí)間來設(shè)置。
可以了解一種“ Continuous Hacking ”的軟件交付方法,請參閱Zach Arnold和Austin Adams的這篇文章。他們建議以下內(nèi)容:
- 創(chuàng)建Docker基本鏡像的白名單,以在構(gòu)建時(shí)進(jìn)行檢查
- 確保你正在拉取的基礎(chǔ)鏡像有加密簽名
- 對推送的鏡像的元數(shù)據(jù)進(jìn)行簽名,以便稍后進(jìn)行檢查
- 在你的容器中,請使用軟件包完整的linux發(fā)行版
- 使用HTTPS拉取第三方依賴
- 不允許在Dockerfile中,將敏感的主機(jī)路徑指定為鏡像中的存儲(chǔ)卷
但是代碼呢?
- 針對已知的代碼級安全漏洞在代碼庫上運(yùn)行靜態(tài)代碼分析
- 運(yùn)行自動(dòng)的依賴檢查程序,以確保你使用的是最新,最安全的依賴版本
- 啟動(dòng)服務(wù),將自動(dòng)滲透機(jī)器人指向正在運(yùn)行的容器,然后看看會(huì)發(fā)生什么
有關(guān)代碼掃描器,請參見OWASP的源代碼分析工具。
7.降低攻擊者的速度
如果有人嘗試使用數(shù)百個(gè)用戶名/密碼組合,攻擊你的API,那么他們可能需要一段時(shí)間才能成功完成身份驗(yàn)證。如果你可以檢測到此攻擊并降低服務(wù)速度,則攻擊者很可能會(huì)消失。
你可以在代碼中或API網(wǎng)關(guān)來實(shí)現(xiàn)速率限制。Okta提供了API速率限制和電子郵件速率限制以幫助降低服務(wù)攻擊。
8.使用Docker Rootless模式
Docker 19.03引入了Rootless模式,此功能,允許用戶以主機(jī)上的非root用戶身份運(yùn)行Docker守護(hù)進(jìn)程(包括容器)。這樣做的好處是,即使受到威脅,攻擊者也將無法獲得對主機(jī)的根訪問權(quán)限。
如果你正在生產(chǎn)中運(yùn)行Docker守護(hù)程序,那么這絕對是你應(yīng)該研究的東西。但是,如果你讓Kubernetes運(yùn)行Docker容器,你需要在PodSecurityPolicy中配置runAsUser。
9.使用基于時(shí)間的安全性
基于時(shí)間的安全性背后的思想是,你的系統(tǒng)永遠(yuǎn)不會(huì)完全安全。防止入侵者只是保護(hù)系統(tǒng)安全的一部分,異常檢測和反應(yīng)也是必不可少的。
使用多因素身份驗(yàn)證可以減慢入侵者的速度,還可以幫助檢測特權(quán)級別較高的人何時(shí)通過關(guān)鍵服務(wù)器進(jìn)行身份驗(yàn)證。如果你擁有諸如域控制器之類的組件,來控制網(wǎng)絡(luò)流量,那么用戶只要登錄成功,就會(huì)向你的網(wǎng)絡(luò)管理員團(tuán)隊(duì)發(fā)送警報(bào)。
這只是嘗試檢測異常,并對異常做出快速反應(yīng)的一個(gè)示例。
10.掃描Docker和Kubernetes配置中的漏洞
Docker容器在微服務(wù)架構(gòu)中非常受歡迎。Docker Image Security10個(gè)最佳實(shí)踐建議:
- 優(yōu)先選擇基本鏡像
- 使用USER指令,確保使用了最少特權(quán)
- 簽名和驗(yàn)證鏡像,以減少M(fèi)ITM攻擊
- 查找,修復(fù)開源漏洞
- 不要將敏感信息泄漏到Docker鏡像
- 使用固定標(biāo)簽實(shí)現(xiàn)不變性
- 使用COPY代替ADD
- 使用元數(shù)據(jù)標(biāo)簽,例如maintainer和securitytxt
- 使用多階段構(gòu)建來獲取小而安全的鏡像
- 使用像hadolint這樣的 linter 檢查工具
11.了解云和集群安全性
如果你正在管理集群和云,那么你可能已經(jīng)知道4C的Cloud Native Security了。
僅在代碼級別解決安全問題,幾乎不可能防范云,容器和代碼中的安全漏洞。但是,當(dāng)你正確地處理這些問題時(shí),就會(huì)為代碼增加安全性,并將增強(qiáng)本已強(qiáng)大的基礎(chǔ)設(shè)施。
Kubernetes博客上有篇文章,標(biāo)題為《防止攻擊的11種方法》。
- 隨處使用TLS
- 啟用具有最低權(quán)限的RBAC,禁用ABAC并使用審核日志記錄
- 使用第三方身份驗(yàn)證程序(例如google,GitHub或Okta)
- 分布式部署你的etcd群集,并為其提供防火墻
- 旋轉(zhuǎn)加密密鑰(Rotate Encryption Keys)
- 使用Linux安全功能和受限制的 PodSecurityPolicy
- 靜態(tài)分析YAML
- 以非root用戶身份運(yùn)行容器
- 使用網(wǎng)絡(luò)策略(以限制Pod之間的流量)
- 掃描鏡像并運(yùn)行IDS(入侵檢測系統(tǒng))
- 運(yùn)行服務(wù)網(wǎng)格
這篇文章雖然發(fā)布在2018年7月,但我認(rèn)為,仍然適用于今天的云原生世界。
總結(jié)
這些安全模式,前面幾點(diǎn)適用于開發(fā)人員。
- 通過設(shè)計(jì)確保安全
- 掃描依存關(guān)系
- 使用HTTPS
- 使用訪問令牌
- 加密和保護(hù)密鑰
它們的其余部分似乎適用于DevOps人員,或更確切地說適用于DevSecOps。
- 使用交付流水線驗(yàn)證安全性
- 降低攻擊者的速度
- 使用Docker Rootless模式
- 使用基于時(shí)間的安全性
- 掃描Docker和Kubernetes配置中的漏洞
- 了解云和集群的安全性
所有這些模式都是重要的考慮因素,因此,組織應(yīng)該確保開發(fā)人員和DevSecOps團(tuán)隊(duì)之間保持密切的關(guān)系。實(shí)際上,如果你選擇了微服務(wù)架構(gòu),那么這些人就不會(huì)在單獨(dú)的團(tuán)隊(duì)中!