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






