Addo Zhang
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # BeyondCorp 第三部分:访问代理 本文详细介绍了 BeyondCorp 前端基础架构的实现。重点介绍了访问代理(Access Proxy),我们在其实施过程中遇到的挑战以及在设计和推出过程中所学到的经验教训。我们还涉及了一些当前正在进行的项目,以提升员工访问内部应用程序的整体用户体验。 在迁移到 BeyondCorp 模型时(前文已讨论过的“BeyondCorp:企业安全的新方法”[^1] 和“BeyondCorp:Google 的设计与部署”[^2]),Google 需要解决许多问题。如何在所有仅限内部的服务中执行公司策略成为一个显著的挑战。传统的方法可能会将每个后端与设备信任推断器集成,以评估适用的策略;然而,这种方法会显著减慢我们能够推出和更改产品的速度。 为了应对这个挑战,Google 实施了一个集中的策略执行前端访问代理(Access Proxy,AP)来处理粗粒度的公司策略。我们实现的 AP 足够通用,可以使用相同的 AP 代码库来实现逻辑上不同的网关。目前,访问代理同时实现了 [^2] 中讨论的 Web 代理和 SSH 网关组件。由于 AP 是唯一允许员工访问内部 HTTP 服务的机制,我们要求所有内部服务都迁移到 AP 后面。 不出所料,仅处理 HTTP 请求的初步尝试证明是不够的,因此我们不得不针对其他协议提供解决方案,其中许多协议需要端到端加密(例如 SSH)。这些额外的协议需要进行一些客户端的更改,以确保设备能够正确地识别给 AP。 AP 和访问控制引擎(共享的 ACL 评估器)的组合提供了两个主要的优势。通过为所有请求提供一个共同的日志记录点,它使我们能够更有效地进行取证分析。我们还能够比以前更快、更一致地更改执行策略。 ## BeyondCorp 的前端基础架构 任何大规模部署的现代 Web 应用都会使用前端基础架构,通常是负载均衡器和/或反向 HTTP 代理的组合。企业级 Web 应用也不例外,前端基础架构提供了部署策略执行点的理想位置。因此,Google 的前端基础架构在 BeyondCorp 的访问策略执行中占据了关键位置。 Google 前端基础架构的主要组件是一组称为 Google 前端(GFE)的 HTTP/HTTPS 反向代理。GFE 提供了许多好处,例如负载均衡和 TLS 处理作为一种服务。因此,Web 应用的后端可以专注于处理请求,而无需过多关注请求的路由细节。 BeyondCorp 利用 GFE 作为逻辑上的集中式访问策略执行点。通过以这种方式引导请求,我们自然地扩展了 GFE 以提供其他功能,包括自助配置、身份验证、授权和集中日志记录。所得到的扩展 GFE 称为**访问代理**(AP)。下一部分详细介绍了访问代理提供的服务的特定内容。 ### 扩展 GFE 的功能:产品需求 GFE 提供了一些内置的好处,这些好处并非专门为 BeyondCorp 设计:它既为后端提供了负载均衡,又通过将 TLS 管理委托给 GFE 来处理 TLS。AP 通过引入身份验证和授权策略来扩展 GFE。 #### 身份验证 为了正确授权请求,访问代理(AP)需要识别发出请求的用户和设备。在多平台环境中对设备进行身份验证会面临一些挑战,我们将在后面的部分“多平台身份验证的挑战”中讨论这些问题。本节重点介绍用户身份验证。 AP 通过与 Google 的身份提供者(IdP)集成来验证用户身份。考虑到要求后端服务更改其身份验证机制以使用AP机制缺乏可扩展性,因此 AP 需要支持一系列身份验证选项:OpenID Connect、OAuth 和一些自定义协议。 AP 还需要处理没有用户凭据的请求,例如软件管理系统尝试下载最新的安全更新。在这些情况下,AP 可以禁用用户身份验证。 当 AP 对用户进行身份验证后,它在将请求发送到后端之前会去除凭据。这样做的原因有两个关键点: - 后端无法通过访问代理重播请求(或凭据)。 - 代理对后端是透明的。因此,后端可以在访问代理的流程之上实施自己的身份验证流程,并且不会观察到任何意外的 cookie 或凭据。 #### 授权 在 BeyondCorp 世界中,我们的授权机制实施受到两个设计选择的推动: - 通过可远程过程调用(RPC)查询的集中访问控制列表(ACL)引擎 - 一种既可读又可扩展的领域特定语言来表达 ACL 将 ACL 评估作为一种服务提供可以确保多个前端网关(如 RADIUS 网络访问控制基础设施、AP 和 SSH 代理)之间的一致性。 提供集中授权既有好处又存在一些缺点。一方面,授权前端使得后端开发人员无需处理授权的详细细节,通过提倡一致性和集中的策略执行点,解放了后端开发人员。另一方面,代理可能无法强制执行细粒度的策略,这些策略在后端处理更好(例如,“用户 A 被授权修改资源 B”)。 根据我们的经验,将 AP 中的粗粒度集中授权与后端中的细粒度授权相结合,可以兼顾两者的优点。这种方法不会导致太多的工作重复,因为应用程序特定的细粒度策略往往与由前端基础架构强制执行的企业范围策略基本上是正交的。 #### 代理与后端之间的双向认证 由于后端将访问控制委托给前端,因此后端必须能够相信其接收到的流量已经经过前端的身份验证和授权。这一点尤为重要,因为 AP 终止了 TLS 握手,后端通过加密通道接收到 HTTP 请求。 满足这个条件需要一种能够建立加密通道的双向认证方案,例如可以实施双向 TLS 认证和企业公钥基础架构。我们的解决方案是一个内部开发的认证和加密框架,名为 LOAS(Low Overhead Authentication System),可以实现代理到后端之间的双向身份验证和加密所有通信。 双向认证和加密前端与后端之间的通信的一个好处是后端可以信任 AP 插入的任何附加元数据(通常以额外的 HTTP 标头的形式)。虽然在反向代理和后端之间添加元数据并使用自定义协议不是一种新颖的方法(例如,参见 Apache JServe Protocol [^4]),但 AP 之间的双向认证方案确保元数据无法伪造。 作为额外的优势,我们还可以逐步在AP上部署新功能,这意味着允许后端服务可以通过解析相应的标头来选择加入。我们利用这个功能将设备的信任级别传递给后端服务,后者可以相应地调整响应中提供的详细程度。 #### ACL 语言 为 ACL 实现一个领域特定语言是解决集中授权挑战的关键。这种语言不仅允许我们静态编译 ACL(有助于性能和可测试性),还有助于减少策略和实现之间的逻辑差距。这种策略促进了以下各方责任的分离: - **拥有安全策略的团队:** 负责访问决策的抽象和静态编译规范 - **拥有库存流水线的团队:** 负责根据特定设备和请求访问的用户来具体实现对资源的访问决策(有关库存流水线的更多细节,请参阅 [2]) - **拥有访问控制引擎的团队:** 负责评估和执行安全策略 ACL 语言使用首次匹配语义,类似于传统防火墙规则。虽然这种模型会产生一些经过深入研究的边界情况(例如,相互覆盖的规则),但我们发现安全团队相对容易理解这种模型。我们目前强制执行的 ACL 结构由两个宏部分组成: - **全局规则:** 通常是粗粒度的,影响所有服务和资源。例如,“低层级的设备不允许提交源代码”。 - **特定服务的规则:** 针对每个服务或主机名的具体规则;通常涉及对用户的断言。例如,“组 G 中的供应商被允许访问 Web 应用程序 A”。 上述假设是服务所有者能够识别出需要制定策略的 URL 空间的部分。除了一些在请求体中进行区分的情况(尽管可以修改 AP 来处理这种情况),服务所有者几乎总是能够识别出这些部分。与特定服务的规则相关的 ACL 部分随着 Access Proxy 为越来越多需要专门 ACL 的业务服务提供支持而逐渐增大。 全局规则集在安全升级(例如员工离职)和事件响应(例如浏览器漏洞或设备丢失)时非常有用。例如,这些规则帮助我们成功缓解了与我们 Chrome 浏览器一起发货的第三方插件的零日漏洞。我们创建了一个新的高优先级规则,将过时版本的 Chrome 重定向到一个带有更新说明的页面,该规则在 30 分钟内在整个公司范围内部署和强制执行。结果,受漏洞影响的浏览器的使用人数迅速下降。 #### 集中式日志记录 为了进行适当的事件响应和取证分析,将所有请求记录到持久存储中至关重要。访问代理(AP)提供了一个理想的日志记录点。我们记录请求头的子集、HTTP 响应代码以及与调试或重建访问决策和 ACL 评估过程相关的元数据。这些元数据包括与请求关联的设备标识符和用户身份。 ### 访问代理的特点:操作可扩展性 #### 自助配置 一旦访问代理基础架构就位,企业应用程序的开发人员和所有者就有动力配置其服务以通过代理访问。 当 Google 逐渐限制用户对企业资源的网络级访问时,大多数内部应用程序所有者将访问代理视为在迁移进行时保持其服务可用的最快解决方案。很明显,一个团队无法扩展以处理对 AP 配置的所有更改,因此我们设计了 AP 的配置,以便促进自助添加。用户保留其配置片段的所有权,而拥有 AP 的团队则拥有编译、测试、金丝雀部署和配置发布的构建系统。 这种设置有几个主要的好处: - 使 AP 所有者无需根据用户请求不断修改配置 - 鼓励服务所有者拥有自己的配置片段(并为其编写测试) - 在开发速度和系统稳定性之间找到合理的折中方案 在 AP 后面设置服务所需的时间已经有效缩短到几分钟,而用户也能够在不需要 AP 团队的支持的情况下迭代其配置片段。 ## 多平台身份验证的挑战 现在我们已经描述了 BeyondCorp 前端的服务器端 - 实现及其带来的挑战和复杂性 - 现在我们将对这个模型的客户端部分进行类似的观察。 至少,要进行正确的设备识别,需要两个组成部分: - 设备标识符的某种形式 - 跟踪任何给定设备的最新已知状态的库存数据库 BeyondCorp 的一个目标是用适当的设备信任替代对网络的信任。每个设备必须具有一致的、不可克隆的标识符,而设备的软件、用户和位置信息必须集成在库存数据库中。正如在前面的 BeyondCorp 论文中讨论的那样,构建和维护设备库存可能非常具有挑战性。下面的小节详细描述了与设备识别相关的挑战和解决方案。 ### 台式机和笔记本电脑 台式机和笔记本电脑使用 X.509 机器证书和相应的私钥存储在系统证书存储中。密钥存储是现代操作系统的标准功能,它确保通过 AP 与服务器通信的命令行工具(和守护程序)能够与正确的设备标识符进行一致匹配。由于 TLS 要求客户端提供私钥拥有权的加密证明,这种实现使得标识符无法伪造和克隆,前提是它存储在安全硬件(如可信平台模块 TPM)中。 这种实现有一个主要的缺点:证书提示通常会让用户感到沮丧。值得庆幸的是,大多数浏览器支持通过策略或扩展自动提交证书。如果服务器拒绝了 TLS 握手时客户端提供的无效证书,用户也可能感到沮丧。失败的 TLS 握手会导致特定于浏览器的错误消息,无法自定义。为了缓解这个用户体验问题,AP 接受没有有效客户端证书的 TLS 会话,并在需要时呈现一个 HTML 拒绝页面。 ### 移动设备 上述讨论的减少证书提示的策略在主要移动平台上并不存在。与其依赖证书,我们使用移动操作系统本身提供的强大设备标识符。对于 iOS 设备,我们使用 ForVendor 标识符,而 Android 设备使用企业移动管理应用程序报告的设备 ID。 ### 特殊情况和异常情况 尽管在过去几年中,我们已经成功将绝大多数 Web 应用程序迁移到了访问代理,但一些特殊用例要么不适应该模型,要么需要某种特殊处理。 ### 非 HTTP 协议 Google 的许多企业应用程序采用需要端到端加密的非 HTTP 协议。为了通过 AP 提供这些协议,我们将它们包装在 HTTP 请求中。 将 SSH 流量包装在 HTTP over TLS 中很容易,得益于现有的 ProxyCommand 功能。我们开发了一个本地代理,它看起来很像 Corkscrew,只是将字节包装成了 WebSockets。虽然 WebSockets 和 HTTP CONNECT 请求都允许 AP 应用 ACL,但我们选择使用 WebSockets 而不是 CONNECT,因为 WebSockets 在本质上继承了浏览器的用户和设备凭证。 对于 gRPC 和 TLS 流量,我们将字节包装在一个 HTTP CONNECT 请求中。包装的明显缺点是对传输协议施加了(可忽略的)性能损耗。然而,它有一个重要的优点, 即在协议堆栈的不同层次上将设备识别和用户识别分开。基于库存的访问控制是一个相对较新的概念,因此我们经常发现现有的协议在用户身份验证方面具有本地支持(例如,LOAS 和 SSH 都提供此功能),但将其与设备凭证扩展在一起是不简单的。 因为我们在包装 CONNECT 请求的 TLS 层上执行设备识别,所以我们不需要修改应用程序使其了解设备证书。考虑 SSH 用例:客户端和服务器可以使用 SSH 证书进行用户身份验证,但 SSH 并不本地支持设备身份验证。此外,修改 SSH 证书以传递设备识别将是不可能的,因为 SSH 客户端证书是可移植的设计:它们预计将在多个设备上使用。类似于我们处理 HTTP 的方式,CONNECT 的包装确保我们正确地区分了用户和设备身份验证。虽然我们使用 TLS 客户端证书对设备进行身份验证,但我们可能使用用户名和密码对用户进行身份验证。 ### 远程桌面 Chrome Remote Desktop 是 Google Chrome 代码库中公开提供的主要远程桌面解决方案 [^5]。虽然在许多情况下在 HTTP 中包装协议是可行的,但是一些协议(例如远程桌面)对由通过 AP 路由导致的附加延迟特别敏感。 为了确保请求得到正确的授权,Chrome Remote Desktop 在连接建立流程中引入了一个基于 HTTP 的授权服务器。该服务器充当了 Chromoting 客户端和 Chromoting 主机之间的授权第三方,同时还帮助这两个实体共享一个密钥,类似于 Kerberos 的操作方式。 我们将授权服务器实现为 AP 的简单后端,并带有自定义的 ACL。这个解决方案已经证明非常有效:每个远程桌面会话只需承担一次 AP 带来的额外延迟,而访问代理可以在每个会话创建请求上应用 ACL。 ### 第三方软件 第三方软件经常会带来麻烦,有时它们无法提供 TLS 证书,有时又假设直接连接。为了支持这些工具,我们开发了一种自动建立加密点对点隧道(使用 TUN 设备)的解决方案。软件对隧道一无所知,并且表现得好像直接连接到服务器一样。隧道建立机制在概念上与远程桌面的解决方案类似: - 客户端运行一个助手来设置隧道。 - 服务器也运行一个充当 AP 的后端的助手。 - AP 执行访问控制策略,促进客户端和服务器助手之间的会话信息和加密密钥的交换。 ## 经验教训 ### ACL 是复杂的 我们推荐以下最佳实践来减轻与 ACL 相关的困难: - **确保语言是通用的。** AP 的 ACL 已经多次改变,我们不得不添加新的数据源(例如,用户和组源)。预计您将需要定期更改可用功能,并确保语言本身不会妨碍这些更改。 - **尽早启动 ACL。** 这样做的原因有两个: - 确保用户尽早熟悉 ACL 以及可能导致拒绝访问的原因。 - 确保开发人员开始调整他们的代码以满足 AP 的要求。例如,我们不得不实现一个 cURL 替代品来处理用户和设备身份验证。 - **使修改成为自助服务。** 如前所述,负责管理特定服务配置的单个团队无法支持多个团队。 - **创建一种从 AP 传递数据到后端的机制。** 如上所述,AP 可以安全地将附加数据传递给后端,以允许其执行精细的访问控制。要早早规划这个所需的功能。 ### 紧急情况会发生 为处理不可避免的紧急情况制定经过充分测试的计划。确保考虑以下两个主要类别的紧急情况: - **生产紧急情况:** 由请求处理路径中关键组件的故障或故障引起。 - **安全紧急情况:** 由紧急需要向特定用户和/或资源授予/撤销访问权限引起。 #### 生产紧急情况 为了确保 AP 能够在大多数故障中保持正常运行,根据 SRE 的最佳实践 [^3] 进行设计和操作。为了在潜在的数据源故障中保持正常运行,我们的所有数据都定期进行快照,并且可以在本地访问。我们还设计了不依赖 AP 的 AP 修复路径。 #### 安全紧急情况 安全紧急情况比生产紧急情况更加微妙,因为在设计访问基础设施时很容易忽视它们。一定要考虑 ACL 推送频率和 TLS 问题对用户/设备/会话撤销的影响。 撤销用户相对比较简单:撤销用户时,会自动将其添加到一个特殊组中,并且在 ACL 中的一个早期全局规则(请参见上文的“ACL 语言”部分)确保这些用户被拒绝访问任何资源。类似地,会话令牌(例如 OAuth 和 OpenID Connect 令牌)和证书有时会泄漏或丢失,因此需要撤销。 如第一篇 BeyondCorp 文章所讨论的,设备标识符在设备库存管道报告之前是不可信的。这意味着即使丢失证书颁发机构(CA)密钥(这意味着无法撤销证书),也不意味着失去控制,因为只有在正确地编目在库存管道中的新证书之后才会信任它们。 鉴于这种能力,我们决定完全忽略证书吊销:我们不发布证书吊销列表(CRL),而是将证书视为不可变的,并简单地降低库存信任级别,如果我们怀疑相应的私钥丢失或泄漏。实质上,库存充当接受的设备标识符的白名单,对 CRL 没有实时依赖。这种方法的主要缺点是可能引入额外的延迟。但是,通过在库存和访问代理之间工程快速传播,这种延迟相对容易解决。 为了确保及时执行策略,您需要一套标准的快速推送 ACL 的过程。在一定规模以上,您必须将至少部分 ACL 定义过程委托给服务所有者,这必然会导致错误。尽管单元测试和烟雾测试通常可以捕捉到明显的错误,但逻辑错误会穿过保护措施并进入生产环境。工程师能够快速回滚 ACL 更改以恢复丢失的访问权限或锁定意外的广泛访问权限是很重要的。举个例子,早期的零日漏洞插件,我们之所以能够 迅速推送 ACL 对我们的事件响应团队至关重要,因为我们可以快速创建一个自定义 ACL 来强制用户进行更新。 ### 工程师需要支持 过渡到 BeyondCorp 模式不会一蹴而就,需要多个团队之间的协调和互动。在大型企业规模下,不可能将整个过渡委托给一个团队。迁移可能涉及一些不兼容的更改,需要充分的管理支持。 过渡的成功在很大程度上取决于团队成功将其服务设置在 Access Proxy 后面的难易程度。让开发人员的工作更容易应该是主要目标,因此应尽量减少意外情况的发生。提供合理的默认设置,为最常见的用例创建步骤指南,并投资于文档。为更高级和复杂的更改提供沙盒环境,例如可以设置 Access Proxy 的单独实例,负载均衡器故意忽略它,但开发人员可以访问(例如,暂时覆盖其 DNS 配置)。沙盒在许多情况下都证明非常有用,例如当我们需要确保在对 X.509 证书或底层 TLS 库进行重大更改后,客户端能够处理 TLS 连接。 ## 展望未来 虽然我们在 BeyondCorp 的前端实现在很大程度上取得了成功,但仍然存在一些痛点。最明显的是,桌面和笔记本电脑使用证书进行身份验证,而移动设备使用不同的设备标识符。证书更替仍然是一个痛点,因为呈现新证书需要重新启动浏览器,以确保现有的套接字被关闭。 为了解决这两个问题,我们计划将桌面和笔记本电脑迁移到移动设备模型,这将消除证书的需求。为了进行迁移,我们计划构建一个桌面设备管理器,它将与移动设备管理器非常相似。它将提供一个共同的标识符,即设备 - 用户 - 会话 -ID(DUSI),该标识符将在所有浏览器和使用共同 OAuth 令牌授权守护程序的工具中共享。一旦迁移完成,我们将不再需要通过证书对桌面和笔记本电脑进行身份验证,并且所有的控制都可以使用 DUSI 在所有操作系统上保持一致。 ## 结论 作为 BeyondCorp 核心组件的 Access Proxy 的实施是针对我们的基础架构和用例的。我们最终实现的设计与常见的 SRE 最佳实践高度一致,已经证明非常稳定和可扩展 - 在部署过程中,AP 的规模增长了数个数量级。 任何寻求实施类似安全模型的组织都可以应用创建和部署类似 AP 的解决方案的基本原则。我们希望通过分享我们在多平台身份验证、特殊情况和异常处理方面的解决方案以及在该项目中学到的经验,能够帮助其他组织以最小的痛苦采取类似的解决方案。 ## 参考文献 [^1]: R. Ward and B. Beyer, “BeyondCorp: A New Approach to Enterprise Security,”*;login:,* vol. 39, no. 6 (December 2014): <https://www.usenix.org/system/files/login/articles/login_dec14_02_ward.pdf>. [^2]: B. Osborn, J. McWilliams, B. Beyer, and M. Saltonstall, “BeyondCorp: Design to Deployment at Google,”*;login:,* vol. 41, no. 1 (Spring 2016): <https://www.usenix.org/system/files/login/articles/login_spring16_06_osborn.pdf>. [^4]: Apache JServer Protocol: <https://tomcat.apache.org/connectors-doc/ajp/ajpv13ext.html>. [^5]: <https://src.chromium.org/viewvc/chrome/trunk/src/remoting/>. [^3]: B. Beyer, C. Jones, J. Petoff, and N. Murphy, eds., *Site Reliability Engineering* (O’Reilly Media, 2016).

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully