如果你正在开发一个大型/复杂的应用,并且你经常需要快速,可靠地升级部署 ,那么微服架构是一个不错的选择。
但是如何提高微服务架构的安全性呢?
设计安全,意味着从一开始就应该将安全性纳入软件设计中。关于安全,其中最常见的一个威胁就是恶意字符。
我问我的朋友罗伯·温奇(Rob Winch)他对删除恶意字符的想法。Rob是Spring Security项目的负责人,被广泛认为是安全专家。
我认为从一开始就将代码设计为安全是有意义的。但是,完全删除恶意字符不可能的。
什么是恶意字符,实际上取决于它所使用的上下文。只是要找出是否存在其他注入
攻击(即JAVAScript,SQL等),你就可以确保html上下文中没有恶意字符。需要注意的是,HTML文档的编码也是基于上下文的。
限制字符也不总是可行的。在许多情况下,软件认为是恶意的字符,但这在某人的名字中是完全有效的字符。那应该怎么办?
我觉得,最好在使用字符的上下文中判断,而不是尝试限制字符。
—罗伯·温奇
作为工程师,我们很早就明白了–创建精心设计的软件体系结构的重要性。软件开发中常见的安全威胁,促使组织在系统架构时要时刻考虑软件的安全性。系统要能够在受到攻击时,也要有用于执行必要的身份验证,授权,数据加密,数据完整性和可用性的解决方案。
从InfoQ文章分析中,我们可以看到:OWASP Top 10在过去十年中并没有发生太大变化。SQL注入仍然是最常见的攻击。十年来,我们仍在继续重复同样的错误。— Johnny Xmas
这就是为什么需要将安全预防措施纳入软件架构的原因。
开源的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);
}
}
但是,此代码仍然有问题。
更好的设计是创建一个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);
}
这样,你的设计使开发人员更容易编写安全代码。
我们用于开发软件的许多类库,很多都依赖于第三方类库,传递性依赖性有时会产生大量的依赖链,其中一些可能就有安全漏洞。
你可以在代码存储库上,使用扫描程序来识别易受攻击的依赖项。你也应该在部署的流水线,主要代码行,发布的代码版本和新的代码贡献中扫描漏洞。
Snyk调查:25%的项目未报告安全问题;多数只添加发行说明;只有10%的人报告CVE。
如果你是GitHub用户,则可以使用dependabot通过pull请求提供自动更新。GitHub还可以在存储库中启用安全警报。
你还可以使用功能更全的解决方案,例如Snyk和JFrog Xray。
你应该在所有地方都使用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 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 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是用于构建云原生和微服务应用程序的下一代的响应式的第5层应用程序通信协议。
这是什么意思?这意味着RSocket具有内置的响应式语义,因此它可以与客户端可靠地通信。RSocket网站介绍,它可应用于 Java,JavaScript,Go, .NET, C++, 和Kotlin中。
Spring Security 5.3.0完全支持RSocket应用程序。
要了解有关RSocket的更多信息,我建议阅读RSocket入门:Spring Boot Server。
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保护服务安全,使用的还是授权服务器。典型的设置是多对一关系,在这种关系中,你有许多微服务与授权服务器通信。
这种方法的优点:
缺点:
另一种更安全的替代方法是一对一方法,其中每个微服务都绑定到其自己的授权服务器。如果他们需要相互通信,则需要在信任之前进行注册。
这种体系结构使你可以明确定义安全边界。但是,它比较慢,也难于管理。
我的建议:使用多对一关系,直到你有计划和文档来支持一对一关系为止。
在过去的几年中, JSON Web Tokens (JWT) 变得非常流行,但也遭到了抨击。主要是因为许多开发人员尝试使用JWT,来避免会话的服务器端存储。请参阅为什么不建议使用JWT。
我的同事Randall Degges和Brian Demers在PASETO( platform-agnostic security tokens)上写了一些有益的文章。
长话短说:使用PASETO令牌并不像听起来那么容易。如果你想编写自己的安全性,则可以使用它。但是,如果你要使用知名的云提供商,则很可能它还不支持PASETO标准。
当你开发与授权服务器或其他服务通信的微服务时,这些微服务可能会存储用于通信的密钥。这些密钥可能是API密钥,客户密钥或用于基本身份验证的凭据。
要更安全地使用密钥,第一步是将其存储在环境变量中。但这只是开始,你应该尽力加密你的密钥。
在Java世界中,我最熟悉HashiCorp Vault和Spring Vault。
下图展示的是Amazon KMS是如何工作。
简而言之,它的工作方式是:
这样,你就无需担心保护密钥的安全性-密钥始终是唯一且安全的。你还可以使用Azure KeyVault来存储你的密钥。
依赖关系和容器扫描,从源头保障了程序的安全,但是在执行CI(持续集成)和CD(持续部署)流水线时,还应该执行测试。
Atlassian有篇文章,DevSecOps:将安全性注入CD流水线,建议使用安全性单元测试,静态分析安全性测试(SAST)和动态分析安全性测试(DAST)。
你的代码交付流水线可以自动执行这些安全检查,但是可能会花费一些时间来设置。
可以了解一种“ Continuous Hacking ”的软件交付方法,请参阅Zach Arnold和Austin Adams的这篇文章。他们建议以下内容:
但是代码呢?
有关代码扫描器,请参见OWASP的源代码分析工具。
如果有人尝试使用数百个用户名/密码组合,攻击你的API,那么他们可能需要一段时间才能成功完成身份验证。如果你可以检测到此攻击并降低服务速度,则攻击者很可能会消失。
你可以在代码中或API网关来实现速率限制。Okta提供了API速率限制和电子邮件速率限制以帮助降低服务攻击。
Docker 19.03引入了Rootless模式,此功能,允许用户以主机上的非root用户身份运行Docker守护进程(包括容器)。这样做的好处是,即使受到威胁,攻击者也将无法获得对主机的根访问权限。
如果你正在生产中运行Docker守护程序,那么这绝对是你应该研究的东西。但是,如果你让Kubernetes运行Docker容器,你需要在PodSecurityPolicy中配置runAsUser。
基于时间的安全性背后的思想是,你的系统永远不会完全安全。防止入侵者只是保护系统安全的一部分,异常检测和反应也是必不可少的。
使用多因素身份验证可以减慢入侵者的速度,还可以帮助检测特权级别较高的人何时通过关键服务器进行身份验证。如果你拥有诸如域控制器之类的组件,来控制网络流量,那么用户只要登录成功,就会向你的网络管理员团队发送警报。
这只是尝试检测异常,并对异常做出快速反应的一个示例。
Docker容器在微服务架构中非常受欢迎。Docker Image Security10个最佳实践建议:
如果你正在管理集群和云,那么你可能已经知道4C的Cloud Native Security了。
仅在代码级别解决安全问题,几乎不可能防范云,容器和代码中的安全漏洞。但是,当你正确地处理这些问题时,就会为代码增加安全性,并将增强本已强大的基础设施。
Kubernetes博客上有篇文章,标题为《防止攻击的11种方法》。
这篇文章虽然发布在2018年7月,但我认为,仍然适用于今天的云原生世界。
这些安全模式,前面几点适用于开发人员。
它们的其余部分似乎适用于DevOps人员,或更确切地说适用于DevSecOps。
所有这些模式都是重要的考虑因素,因此,组织应该确保开发人员和DevSecOps团队之间保持密切的关系。实际上,如果你选择了微服务架构,那么这些人就不会在单独的团队中!