DevSecOps——25页以内的基础知识

214 阅读39分钟

在我职业生涯的后期,我在威斯康星大学史蒂文斯角分校 (UWSP) 的美丽校园内任教。 UWSP 的计算机项目使学生能够在多个学科和重点方向上追求学位。课堂经验表明,学生需要接触到超出其专业或重点领域的主题。例如,专注于开发的专业学生从命令行工具和传统上以运维为重点的领域(如 DNS)的经验中获益。同样,网络和服务器方向的学生通过学习编程技术来编写脚本也得到了帮助。

如果说有一种所有 DevSecOps 从业者都需要熟悉的通用技术技能,那就是在 shell 或终端中工作,也称为命令行环境。无论是编写脚本和配置文件、运行命令,还是排查日志文件中的错误,命令行都是掌握 DevSecOps 以及你作为开发人员、安全管理员或运维人员的具体角色的关键技能。命令行技能是进入 DevSecOps 更高层次的一个重要区分因素。

本章提供了对许多组织在践行和使用 DevSecOps 实践和流程时遇到的主题的高层概述。如果我对100个有一定计算机经验的人进行调查,那么对于涵盖的主题至少会有101种不同的回应。考虑到这一点,本章不会试图涵盖你在职业生涯或 DevSecOps 旅程中可能需要的所有主题的广度。本章也无法对每个主题提供所需的深度覆盖。整个学位项目的设计就是为了提供这种深度。

本章首先介绍命令行界面,然后继续介绍当今使用的基本网络模型。由于 DNS 在排查故障中的重要性以及在 DevSecOps 团队之间讨论时有助于知识共享,因此本章也将涉及 DNS。

命令行界面

如果要区分计算机专业人员、从事计算机相关工作的人员和铁杆爱好者的不同之处,命令行界面 (CLI) 的使用就是其中的一个重要标志。许多网络和计算设备都有命令行界面,通过它们可以快速高效地管理设备。互联网上最繁忙的网站和骨干服务大多基于 BSD 或 Linux 系统,且不使用或甚至未安装桌面图形用户界面 (GUI)。这使得命令行界面成为工作中的一个重要组成部分。在大多数现代环境中,自动化、配置和脚本编写都围绕命令行界面进行。

命令行与终端与 Shell

我所说的命令行界面有很多名称。你可能会听到命令行、命令行界面、命令提示符、命令行环境、Shell、Shell 提示符、Shell 环境、终端、SSH 或其变体。虽然存在一些关键区别,但这些区别对 DevSecOps 从业者来说大多数都不重要。

Shell 是一个提供计算设备命令行界面的程序。就像有许多看起来功能相同的程序一样,也有许多 Shell 程序。一种常见的 Shell 被称为 Bourne-again shell 或 bash。它足够常见且广泛可用,因此除非另有说明,本书其余部分默认使用 bash。如果你使用的是 Mac 或 Linux,那么你可以通过终端访问已安装的 Shell。值得注意的是,根据 macOS 的版本,默认的 Shell 可能是 bash 或 Z shell。

注意

在谈论 bash 时,大小写问题值得注意。当“bash”一词出现在句首时,需要大写。然而,当将 bash 作为命令引用时,它应为小写,因为在你使用命令或 Shell 的任何系统上它都会是小写。我的建议是:忽略你在引用 bash 时看到的大小写差异。我保证不会以此为借口随意大写“bash”,但你可能会偶尔在本书或互联网上看到“Bash”这个词。在这种格式下,“Bash”和“bash”是相同的,但在使用命令时,命令本身总是小写的。幸运的是,你通常只会在编写脚本时将“Bash”作为开头的行使用。

并非所有的 CLI 都是一样的

Windows 中的命令提示符提供了基于 DOS(磁盘操作系统)的界面,与 Linux 中更强大的 Shell 环境相比,其功能非常有限。Windows 的 PowerShell 是对命令提示符的一种改进,使得对 Windows 环境的完全脚本化访问成为可能。然而,鉴于 Linux 的重要性,微软创建了 Windows Subsystem for Linux (WSL),它提供了几乎完整的 Linux 体验,使 Windows 用户能够通过安装一个或多个流行的 Linux 发行版来利用 CLI。

过去几年中,安装 WSL 的过程不断演变,现在 WSL 已被称为 WSL 2。我还没有找到一种“百试百灵”的方法来在 Windows 系统上安装 WSL,因为在特定的 Windows 系统上可能会安装许多不同的配置和功能。甚至我从比尔·盖茨那里得到的 Windows 安装程序在第一次尝试安装 WSL 2 时也没有成功,且中间还需要进行一系列重启。抛开这些不谈,你可以在 WSL 文档中找到安装 WSL 2 的说明。

为什么我需要命令行?

多年来,我听到过“为什么我需要命令行 [或 SSH 或终端]?”这个问题的各种版本。答案很简单:速度。DevSecOps 从业者的许多(可能是所有)工作都可以通过 CLI 更快、更高效地完成。在使用 CLI 时,你的手不需要离开键盘去点击菜单或按钮。

在编程时,可以使用像 Vim 这样的编辑器完全在键盘的初始位置上操作,而涉及安全和运维的软件将有一个命令来运行,无论是作为 GUI 的补充,还是作为执行程序的唯一手段。

一个非常简单的 CLI 用例,包括安装 WSL Linux,就是创建重要文件的备份。rsync 命令可以用于将文件从本地计算机同步或传输到共享存储或备份的 USB 驱动器。我管理一个 DJ 的音乐库,其中包含大约 2 万个文件,分布在一个深层目录结构中。这些文件以一个大/未压缩的版本存储,然后以几种其他格式存储,文件大小较小。我使用 rsync 命令来创建这些文件的备份。如果网络连接出现问题或进程由于某种原因停止,rsync 将从中断处继续。如果添加了文件,rsync 只会发送更改过的文件,而不是重新发送所有文件。

开始使用命令行

如果你使用的是 Mac 或 Linux,那么你已经可以通过终端窗口使用功能齐全的 CLI。如果你使用的是 Mac,那么在“应用程序”→“实用工具”中找到终端,将直接进入 CLI。访问 Linux 上的 CLI 方式因发行版和访问方式的不同而有所不同。如果有桌面环境,你通常会找到一个终端程序。否则,使用安全 Shell (SSH) 连接到 Linux 计算机也能达到同样的效果。

如果你使用的是 Windows 10 或 11 电脑,那么安装 WSL 2 是一个简单的入门方式。如果你使用的是更早版本的 Windows 或无法安装 WSL 2,那么虚拟化软件如 VirtualBox 可以让你安装一个完整的 Linux 发行版。

在本章后面以及整个书中,将频繁使用命令行。此外,附录 B 包含了一些帮助你在命令行中导航的精选命令。现在,让我们从命令行转向对协议的高层概述。

协议:高级概述

互联网通过一系列协议进行通信。谈到通信时,协议仅仅是对每一方如何行动的约定。想象一下电话中的语音对话。一方拨打电话,接收方接听电话并以某种形式说“你好”或类似的问候语。拨打电话的一方随后回应问候,可能也说“你好”。拨打方可能在问候之后进行自我介绍,并说明通话的目的。

这个叙述描述了语音通话的典型协议,其中每一方都应该以某种方式行事。考虑当协议因某种原因中断时会发生什么情况。有时,在接收方刚拿起电话听筒时会有来电,这样他们可能不知道有来电,因此不会说“你好”来开始对话。更常见的情况是接收方回答“你好”后,接着是一个长时间的停顿,等待自动拨号软件连接到另一端的真人。这引入了协议中的超时概念。当接收方说“你好”时,他们通常期望在短时间内得到回应。如果没有回应,那么这很可能是一通自动拨号的电话。

关于电话对话的协议讨论可以扩展到日常生活中的许多人际互动。没有协议,就会一片混乱。计算机,尤其是网络通信,都是围绕协议的遵从性构建的。就像人际互动一样,这些协议确保了互操作性。如果没有协议,每个供应商都会有自己实现的各种通信方式,而这些通信方法很少会兼容。想要向使用不同供应商产品的人发送电子邮件吗?你需要注册那个网络并使用专有软件来进行操作。

为了避免不可互操作软件的噩梦,互联网主要建立在共同、共享和开放的协议之上。通信的基础是互联网协议(IP),它有多种方式可以从一个点传输到另一个点,从一个设备传输到另一个设备。许多其他常见且基础的网络服务也是类似的,依赖于 IP 作为通信的基础。本节将探讨协议层及相关的网络模型。

协议层

与网络相关的通信通常由两种模型之一表示,即开放系统互连 (OSI) 模型或 TCP/IP 模型。图2-1展示了 OSI 模型。

image.png

TCP/IP 模型与 OSI 模型类似,只是将会话层、表示层和应用层合并为一个单一的应用层,而数据链路层和物理层则合并为一个单一的链路层或本地网络层。图2-2展示了这一模型。

image.png

当设备通过网络通信时,数据从应用程序(如网页浏览器)沿着层次向下传输,最终到达物理介质,如有线以太网或用于 WiFi 的无线信号。接收方然后将数据沿层次向上传递到接收方的对应应用程序。

两种协议及另一个协议

传输控制协议 (TCP) 和用户数据报协议 (UDP) 是你日常最常遇到的协议。TCP 是面向连接的,而 UDP 是无连接的。这种差异意味着,使用 TCP 的应用程序应该会按顺序接收数据包,而使用 UDP 的应用程序则需要负责确保数据包已经到达,并在数据包未到达时请求重新传输。对于 DevSecOps 从业者而言,许多关键内容依赖于 TCP,唯一显著的例外是域名系统 (DNS) 协议,本章稍后将对此进行讨论。

一个与众不同的协议是互联网控制消息协议 (ICMP)。ICMP 是你可能用来验证连接性的 ping 命令背后的协议。本章不会深入探讨 ICMP,仅会做表面介绍。然而,在 ICMP 的背景下,有各种消息类型由路由器和网络设备用于标识网络状态。使用 ping 命令时,你发送的是 ICMP 消息类型 8,即“回声请求”,如果被测设备响应,它会以“回声应答”回应,也就是 ICMP 消息类型 0。

为什么这很重要?

如果你是一名开发人员,正在学习 DevSecOps,可能已经问过:“为什么这些内容重要?”或“它们如何帮助我?”这些问题是合理的。学习协议和协议层很重要,因为 DevSecOps 在每个团队彼此更多了解时最为成功。安全分析师和运维人员必须熟悉协议,至少为了能够在防火墙中打开端口。开发人员可以通过以安全分析师容易理解的方式表达需求来更快地打开这些端口,例如说“此应用程序需要 tcp/443 出站”比“它需要连接到他们的服务器进行激活”更有效。

基础互联网协议

前一节简要定义了协议,并进一步探讨了表示互联网通信分层性质的两种模型。本节将继续探讨协议,涵盖一些在 DevSecOps 组织中经常遇到的基础互联网相关协议。本节从 DNS 开始,它是其他服务赖以存在的基础。值得注意的是,本节和本书确实有意跳过了对协议头的讨论。尽管会讨论 IP 地址,并介绍 TCP 和 UDP 之间的简单区别,但无需熟记 TCP 的三次握手过程。

在本节和本书的其余部分中,你将看到“RFC”或其复数形式“RFCs”这一术语。RFC,或称请求评论 (Requests for Comments),是互联网运行的标准。由互联网工程任务组 (IETF) 创建,RFC 负责将软件和硬件的预期行为编纂成文。有关 RFC 和其背后过程的更多信息,可以在 IETF 网站上找到。

DNS

域名系统 (DNS) 是使我们无需记住 IP 地址就能在互联网上进行通信和获取信息的原因。从技术上讲,名称解析本身实现了这一点,还有其他手段可以提供名称解析。但维护一个包含我可能想要通信的每个主机的文本文件将非常麻烦。因此,DNS 是互联网名称解析的焦点。

DNS 由 RFC 1034 和 1035 定义,提供了一种分层方法,用于共享对域的控制权和命名节点或主机的责任。在 DNS 的根部是一个单一的点,它分支到几个顶级域 (TLDs)。TLDs 是通用的,如 com、net、edu 和其他许多。TLDs 也可以是国家代码顶级域 (ccTLDs),如 uk 或 de。图 2-3 展示了 DNS 的分层结构。

image.png

在顶级域名 (TLD) 向公众开放之前,只有少数几个通用顶级域名。现在有许多通用顶级域名。根区数据库包含当前顶级域名的列表。

个人和组织可以在所需的顶级域名下注册域名,并且必须遵守被授权管理该顶级域名的注册商的规则。在 com 顶级域名下注册一个域名通常为期一年,并且需要支付少量费用。注册域名时,至少需要两个权威名称服务器,在这种情况下,“权威”一词具有特别的含义。

当一个域名被注册时,该域名内的命名控制权被委托给注册人,即注册该域名的个人或组织。与该域名关联的 DNS 服务器是权威服务器,这意味着当有关于该域名主机的查询发送时,该查询会被发送到其中一个权威服务器,然后由其作出相应的回应。

主机名解析

假设主机有一个有效的 IP 地址,并且有一个或多个 DNS 服务器可用,当该主机想要开始在网络上进行通信时,主机需要将友好名称转换为 IP 地址,以确定将消息发送到哪里。大多数计算机和服务器首先会检查存储在本地的一个名为“hosts”的文件。hosts 文件在 macOS 和 Linux 系统中存储在 /etc/ 目录中,在较新版本的 Windows 中则位于 %SystemRoot%\System32\Drivers\etc 目录中。hosts 文件的内容各不相同,但通常包含有关本地主机地址的信息。例如,macOS Big Sur 中的 /etc/hosts 文件包含以下内容:

##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1       localhost
255.255.255.255 broadcasthost
::1             localhost

Android 和 iOS 系统也包含 hosts 文件,但编辑这些文件更为困难,通常不是 DevSecOps 工程师会做的事情。

由于 hosts 文件是首先被查询的,因此有机会劫持或绕过正常的名称解析过程。当开发人员希望请求被发送到他们的本地计算机或与 DNS 中定义的不同的计算机时,这样做可能对开发和测试有帮助。然而,假设目标计算机未在 hosts 文件中定义,则接下来会查询 DNS。

从广义上讲,每个接收到 IP 地址的设备通常还会接收到一个或多个 DNS 服务器,这些服务器代表这些设备从其他 DNS 服务器获取主机名。“通常”和“接收到”这两个词在这里有特殊含义。没有规定必须包含或使用 DNS 服务器。一个主机可以在没有 DNS 服务器的情况下接收 IP 地址并正常通信,前提是主机要么有本地 hosts 文件,要么不使用主机名进行通信。此外,“接收到”一词可以指手动分配 IP 信息或通过动态主机配置协议 (DHCP) 等自动方式获取 IP 信息。

这些 DNS 服务器被称为解析器,因为它们提供主机名解析。如果你正在阅读的设备连接到 WiFi 网络,那么你可能有一个本地网络中的 DNS 服务器,当你尝试访问 google.com 时,它负责获取 IP 地址。

请注意解析器和权威名称服务器之间的区别。解析器负责从客户端设备获取查询答案,即使该解析器不对所查询的域负责或不是权威的。权威名称服务器是一个或多个域的所有者或授权者。解析器也可能是一个或多个域的权威服务器,但从逻辑上讲,主机名解析和权威名称解析是两个不同的概念。从技术上讲,你可能听说过“递归解析”或“递归解析器”这个术语,指的是为你获取答案的 DNS 解析器,但就本章而言,知道解析器和权威名称服务器之间存在区别就足够了。

图2-4展示了从客户端计算机到本地解析器的 DNS 解析过程,解析器随后查询 google.com 的权威服务器以获取 google.com 的 IP 地址。然后回复被返回给解析器,再由解析器将答案传回客户端。

image.png

图2-5 展示了当本地解析器同时也是某个域的权威服务器时会发生什么情况。虽然这种情况对于家庭用户来说比较罕见,但在企业场景中则更加常见,因此对于 DevSecOps 工程师来说,理解这一点尤为重要。

image.png

此外,在企业场景中,可能会存在分割授权的情况,即当从内部查询时,主机名解析为内部 IP 地址,而从外部查询时,解析为不同的 IP 地址。这种情况出现的原因有很多,包括测试的方便性以及控制流量的能力。图2-6 展示了这种情况;请注意,根据查询者的不同,同一个查询会得到不同的答案。

image.png

这些图示有助于说明 DNS 的复杂性,以及为什么在从开发阶段转移到测试阶段再到生产阶段时,DNS 可能会引发困难。

起始授权和生存时间

在这变成一本 DNS 书籍之前(已经有许多这样的书籍,其中包括由 Cricket Liu 和 Paul Albitz 撰写的权威著作《DNS and BIND》(O'Reilly, 2006)),还有一个问题需要简要讨论。每个 DNS 区域都包含一个起始授权 (SOA) 记录,用于定义域的元数据。与 DevSecOps 从业者相关的一些关键点包括:

  • 序列号:一个整数值,每次区域更改时递增。
  • 刷新:辅助 DNS 服务器等待请求更新的时间(以秒为单位)。
  • 重试:辅助服务器在请求未响应的服务器之间应等待的时间(以秒为单位)。
  • 过期:主服务器在不再被视为该域的权威服务器之前可以宕机的时间(以秒为单位)。
  • NX:负面或未找到的答案在递归服务器再次检查之前应缓存的时间(以秒为单位)。

除了 SOA 本身存储的值外,还有一个生存时间 (TTL) 值,它是为整个域配置的,也可以按记录进行配置。TTL 控制递归服务器在再次请求之前缓存有关给定记录的信息的时间。TTL 越长,对权威名称服务器的负载就越小。然而,TTL 越长,递归服务器发现更改所需的时间就越长。

从 DevSecOps 的角度来看,当存在停机时间或需要 DNS 更改的配置更改时,DNS TTL 变得尤为重要。当你知道即将进行的部署需要更改 DNS 记录时,最佳实践是降低受影响记录的 TTL,这样更改传播所需的时间就会减少。较小的 TTL 带来的较短停机时间通常会抵消对 DNS 服务器的较高负载。

通过降低 TTL,你迫使所有地方的解析器请求新的 IP 地址信息。降低 TTL 需要计划,因为现有 TTL 值需要过期,新的 TTL 值才能生效。例如,如果 A 记录的 TTL 为 604,800 秒或 7 天,那么如果你在部署前一晚更改 TTL 并没有多大意义,因为任何请求名称解析的解析器还有 6 天时间才会再次请求。因此,了解现有的 TTL 并计划需要 DNS 更改的部署是必需的。

附录 B 包含一些在故障排除 DNS 时有用的命令。在附录中,你会找到用于确定给定 DNS 记录 TTL 的 dig 命令,以及其他基本的 Linux 命令等。

HTTP

超文本传输协议 (HTTP) 是 DevSecOps 从业者的核心协议。HTTP 是网络的语言,用于在服务之间传输网页和远程编程访问。HTTP 由多个 RFC 定义,但主要是 RFC 9110 和 RFC 9112;在本节中,我将忽略与加密相关的内容,因为这些内容在特定协议层级上基本无关紧要。

HTTP 是一种无状态协议,这意味着客户端(例如你的计算机)向一个会使用 HTTP 的网络服务器发出请求。服务器不会记住上一个请求;每个请求都是新的。当客户端发出请求时,该请求遵循 RFC 中定义的协议。HTTP 交换或消息包括控制数据、头信息、内容和尾部。在实际操作中,当处理 HTTP 时,你最有可能遇到控制数据、头信息和内容,你最有可能听到的是作为 HTTP 头信息一部分的控制数据,这为开发者提供了一个重要的区别。

属于头信息部分的协议元素可能不允许出现在内容中。因此,当响应中的头信息部分关闭后,无法再发送额外的头信息。这有时会成为开发人员在处理会话、Cookie 和其他元素以构建更复杂的 Web 应用程序时的困惑点。

HTTP 交换的开始是客户端向服务器发送请求某个资源(如网页)的请求。该请求除了包含请求本身的元数据外,还包括一个方法或动词。常用的 HTTP 方法是 GET,客户端请求从服务器检索或“获取”资源。如果服务器有该资源,它将以一条消息响应客户端,随后是请求的页面或资源。这里有一个示例:

GET /devops.php HTTP/1.1
Host: www.example.com

在这个基本示例中,使用了 GET 方法请求位于根目录 / 的一个名为 devops.php 的文件。请求中使用的 HTTP 版本为 1.1。请求的下一行是主机头信息。主机头信息在 HTTP 版本 1.1 中被添加,作为在单个 IP 地址上托管多个网站的一种手段。

接收此请求的服务器验证它是否能够处理该请求,然后确定在文档根目录中是否有一个名为 devops.php 的资源。假设服务器不太忙以至于无法处理请求,则会发送 HTTP 响应。如果文件存在且请求成功,服务器将以状态码 200 和原因短语 OK 开始响应。如果发生错误,则根据情况可能返回其他状态码和原因短语。例如,如果在文档根目录中没有名为 devops.php 的文件,则服务器将以状态码 404 和原因短语 Not Found 进行响应。

其他可能的状态码包含在 RFC 9110 中。GET 请求有时可能包含查询字符串。查询字符串是请求中问号之后出现的名称-值对,例如:

GET /devops.php?date=20230803

在此示例中,值 20230803 被发送到服务器,服务器可以根据 devops.php 的内容对此值进行操作或不操作。

使用 POST 方法的请求与 GET 请求基本相同,尽管 POST 请求的头信息通常会包括 Content-Length 和 Content-Type 字段。

从 DevSecOps 从业者的角度来看,HTTP 的大部分内容可能并不重要。然而,当事情出错时,通常是由于服务器配置错误或对协议规则的误解。例如,我曾与一家咨询公司合作,他们为我的雇主设计了一个 Web 应用程序。它们没有使用 HTTP Cookie 和会话,而是将会话浏览信息放在查询字符串中,使其出现在浏览器的地址栏中。当用户浏览该应用程序时,查询字符串越来越长。当部署到生产环境时,应用程序的实际使用场景导致查询字符串增长到超过 10,000 个字符,因此失败,因为该长度超出了大多数浏览器的支持。

在开发面向 Web 或使用标准 Web 浏览器的应用程序时,开发人员熟悉缓存以及围绕缓存的 JavaScript 和层叠样式表的挑战。与 DNS 中的 TTL 类似,最好能请求浏览器不缓存某些元素。是否尊重请求取决于浏览器。避免缓存的一个简单方法是在查询字符串中放置时间戳,使得浏览器认为它正在请求新资源,因此不会评估缓存来使用旧副本。此做法的明显影响是服务器负载更高,因为它需要处理每个可能正常情况下已缓存的请求。

其他协议

在 DevSecOps 从业者的职业生涯中,甚至在日常工作中,他们会遇到许多其他协议。仅讨论了 DNS 和 HTTP 这两种协议,留出了大量供读者进一步学习的空间。选择 DNS 和 HTTP 作为探讨对象是基于工作中与不同技术水平的人员合作的职业经验做出的决定。

即使我选择深入探讨其他六种协议,仍然会有人问:“那关于……”他们最喜欢的协议。我在学习计算机概念的早期读了很多关于互联网分组交换/顺序分组交换 (IPX/SPX) 网络的材料。即使当时已经明确基于 IP 的网络将长期存在,IPX 相关材料仍然被包含在这些材料中。虽然 IPX/SPX 网络本身没有问题,但在当时的书籍中包含这些材料并不是对资源的最佳利用。

考虑到这一点,与其详细讨论其他几个协议,不如分享一份应对 DevSecOps 从业者有所帮助的协议清单:

  • 文件传输协议 (FTP)
    FTP 不安全。凭证和数据都是以明文方式发送的。即使如此,FTP 仍在许多组织内部使用。了解 FTP、SFTP(安全文件传输协议)、FTPS(FTP over SSL)和 SCP(安全复制协议)之间的区别,以及知道你的组织支持哪些协议,将是有用的知识。

  • 安全 Shell (SSH)
    对于 SSH,有很多东西需要学习,同时也有很多不需要学习。DevSecOps 团队中的某些人会使用 SSH 远程配置服务器。了解如何使用 SSH 密钥和端口转发会非常有帮助,但了解协议本身的内部工作原理可能并不必要。

    提示
    知道如何从远程位置安全地重启 SSH 服务器可以为你节省数小时的旅行时间。

  • 简单网络管理协议 (SNMP)
    你可能永远不会遇到 SNMP,但 DevSecOps 团队中可能会出现使用 SNMP 的监控基础设施。像 SSH 一样,SNMP 有很多内容需要学习,同时也不需要学习很多内容。

接下来,让我们深入探讨一些关于数据安全的基础知识。

数据安全:机密性、完整性和可用性

经典的安全三元组包括机密性、完整性和可用性(CIA)。当计算机和数据安全问题出现时,这些问题通常可以追溯到上述三者中的一个或多个。坐在你旁边的那个人与您一起看着您的手机,违反了机密性原则。同样,偷听他人对话但并非对话参与者的行为也属于对机密性的侵犯。

上一节描述的每个协议模型的每一层都代表了一个可能的攻击点,可能会违反机密性、完整性和可用性中的一个或多个。同样,每一层也代表了一个可以添加或使用安全流程的点,以减少攻击的可能性、减轻攻击的影响,或有时完全消除攻击的威胁。

在这三个原则中,机密性受到了极大的关注,可能是因为失去机密性时的影响特别明显。而完整性和可用性并不因此显得不重要。尤其是可用性,或缺乏可用性,在许多勒索软件攻击中尤为突出,在这些攻击中,数据被加密,只有通过密钥才能解锁。让我们更详细地看一下每个原则:

机密性 本节前面提到的例子,如某人查看您的手机或偷听您的对话,是现实生活中破坏机密性的简单情况。在像美国这样的地方,在公共场所通常没有隐私期望,虽然窥探他人手机等问题可能会因情况而异进行处理。在私人空间的室内,隐私则是可以期待的。

机密性适用于传输中的数据和静态数据。传输中的数据通常被认为是通过网络传输的数据。在 WiFi 的出现和普及之前,访问传输中的大量机密数据要困难得多,通常需要通过物理手段进入数据传输的设施。

完整性 与关注他人查看数据的机密性不同,完整性关注的是确保数据维持在已知的良好状态。与机密性不同,攻击者并不需要查看数据。相反,仅仅能够更改数据本身就足以违反完整性。例如,考虑一种情况,攻击者无法看到结果,但能够通过用假流量压垮接收方来随机翻转数据流中的位。因此,攻击者可以随机更改诸如医疗测试结果或其他重要数据的内容。

可用性 可用性,或缺乏可用性,指的是您无法按照预期方式访问或使用计算机系统、设施和数据的情况。这个定义包括物理攻击(如有人偷走你的笔记本电脑)以及诸如拒绝服务 (DoS) 攻击等问题,其中计算服务在技术上可以访问,但性能下降到使得服务实际上无法达到预期目的。

对于提供 CIA 的更广泛问题,DevSecOps 团队成员在提供物理安全或其他确保安全性维护所需的元素方面能做的事情是有限的。对于那些刚接触安全概念的人来说,OWASP(开放 Web 应用程序安全项目)前十大安全风险是一个很好的起点。

尽管主要关注 Web 应用程序,但 OWASP 前十大安全风险是一个漏洞类别列表,开发人员、运维人员和安全分析师常常会遇到。OWASP 前十大安全风险包括诸如身份验证失效、跨站脚本、日志记录不足、数据暴露以及其他六个在你阅读本文时可能已经发生变化的类别。任何希望更多了解计算机安全,尤其是那些负责创建应用程序的人,都应从 OWASP 类别中找到有价值的内容——理想情况下,将这些知识带回应用程序开发过程中,并针对其组织中的特定语言和环境进行调整。

第3章将更详细地介绍三元组中每个元素的具体信息,像 DevSecOps 本身一样,涉及安全性的问题将在整本书中有所覆盖。下一节将讨论与 DevSecOps 相关的开发内容。

脚本编写的开发概述

本章的其余部分将重点介绍有助于编写脚本的开发构造,或创建执行一个或多个操作的小程序,例如移动文件、验证连接性或提取日志文件。目标不是替代其他学习机会或知识,而是为深入理解编程概念提供一个起点或踏脚石。

程序是一系列指令,这些指令使计算机执行某个操作。在非常高的层次上,程序旨在解决问题。在组织环境中,程序执行业务功能。在许多情况下,这些功能可以在没有计算机的情况下完成。然而,计算机可以更有效地执行这些功能,因此需要编写程序。例如,早在计算机存在之前,税收就已经被计算出来了。然而,计算机可以被编程以使税收计算变得更容易和更准确。

从最高层到最低层,计算机处理器依赖于指令集来执行高级程序所需的操作。实际上,开发人员可以直接将他们的程序编写为处理器的指令,或者使用一种语言,然后将其解释为处理器所需的指令集。本书关注的是那些高级编程语言。

PHP、JavaScript、Python、Perl、C++ 和 C 是你今天可能使用的常见高级编程语言。对于像 PHP 和 Python 这样的语言,你可以编写全栈 Web 应用程序或后台程序,也可以编写一个较小的程序。

本节特别关注创建 bash 脚本时涉及的语言组件。我假设你有一个 shell 环境可用,特别是 bash。如果没有,请参阅“命令行界面”以了解更多详细信息。

命令和内建命令

在命令行等 shell 环境中工作时,你将使用两个元素:外部命令和内建命令。外部命令是无论你使用什么 shell 程序都存在的命令。例如,cp 程序用于复制文件。cp 程序通常位于 /bin 目录中。如果你使用的是 bash、zsh、ash、tcsh 或其他 shell,cp 程序都可以供你使用。执行时,cp 命令会在计算机上创建一个新进程以完成其任务。

与外部命令 cp 相比,whence 命令则是一个内建命令。whence 命令提供了有关 shell 如何解释给定命令的信息。例如,你可以使用 whence 来确定命令将从哪里执行。这有助于确定命令是内建命令还是外部命令。在 bash 中,whence 命令有一个 -t 选项,但在 zsh 中没有 -t 选项。因此,外部命令和 shell 内建命令之间的主要区别在于,外部命令在所有 shell 环境中都以相同方式工作,而内建命令则可能不适用。

基本编程构造:变量、数据和数据类型

本节的目标是编写一个 shell 脚本。然而,本节中的许多概念适用于通用编程,无论使用何种语言。事实上,某些概念在 bash 中无法实现。

在创建程序时,程序很可能需要处理各种数据。变量是用于在程序中存储数据以供以后使用的一种手段。在 bash 中,变量的定义和赋值使用以下语法:

variable=value

例如,创建一个名为 username 的变量,包含名为 rob 的账户用户名,如下所示:

username="rob"

注意变量名、等号和值之间没有空格。添加空格会导致脚本失败。

在 bash 中访问变量的方法是给变量前面加上一个美元符号。例如,使用 echo 命令打印 username 变量的内容,如下所示:

echo $username

注意 在 Linux 和 macOS 中,shell 脚本默认不可执行。相反,需要为文件设置执行权限。简单介绍 Unix 权限的复杂性,在示例代码的情况下,为你的用户添加执行权限的命令是 chmod u+x <filename>,其中 <filename> 是要更改的文件名。

变量旨在保存可能会更改的数据。与变量相比,常量在编程术语中用于保存在应用程序执行期间不应更改的值。例如,数据库凭据。在软件运行时,凭据不应更改。在程序中使用常量还有其他好处,比如能够从外部源(如配置文件)注入凭据。在 bash 中,常量使用内建命令 readonly 声明:

readonly username="rob"

许多语言使用只能保存一种类型数据的变量,如数字或字符串。一些语言是强类型的,这意味着语言不允许变量保存多种数据类型。例如,像 Java 这样的强类型语言要求程序员声明变量的类型,并且类型不能更改。如果声明的变量只能保存整数,那么它永远不能保存字符串。

bash 不是强类型的。事实上,bash 根本没有类型。这意味着变量可以保存字符串、数字或其他任何东西。假设程序员知道自己在做什么,并有意在程序中更改了变量的类型。带着意图编程是另一本书的主题。就本书而言,当你创建一个变量来保存数据时,如果需要,bash 将允许你更改该变量所保存的内容。

使用条件语句进行决策

有些程序,尤其是脚本,只需要执行一个在执行时已知的操作,例如,一个用于创建备份或将逗号分隔值 (CSV) 文件导入数据库的 bash 脚本。即使是某些脚本化程序,也需要根据输入或某些外部因素(如星期几或一天中的时间、天气条件或在创建程序时无法预知的其他因素)做出决策。

条件语句用于在程序中进行决策。if 语句是用于此目的的基本构造。if 语句评估条件是否为真。如果条件为真,则执行相应的代码。如果条件不为真,则不执行相应的代码。

在 bash 中创建一个 if 条件语句涉及到一些与其他语言相比较为独特的元素。在程序示例中,变量 username 被赋值为 rob。如果我们想基于该用户名执行一个操作,比如打印“Hello rob”,代码如下所示:

username="rob"
if [ $username == "rob" ]
then
    echo "Hello $username"
fi

注意在 bash 中创建条件语句时需要的新语法元素。if 条件本身用方括号括起来。比较时使用双等号而不是单个等号。虽然在某些情况下单等号在 bash 中是有效的,但许多语言只使用双等号语法,因此为了避免混淆,我建议除非有令人信服的理由,否则使用双等号。要确定变量是否不等于某个值,可以使用 ! 运算符,如下例所示:

if [ $username != "rob" ]
then
    echo "Go Away"
fi

bash 的比较运算符有其特殊性,比较数字时使用不同的术语。具体来说,数字值的相等比较运算符为 -eq(等于),而 -ne 表示不等于。各种形式的大于、小于、大于或等于和小于或等于使用两字母语法。传统的大于和小于符号(><)在 bash 中也被使用,但这些字符用于比较字符串的排序顺序,而不是比较数字。表 2-1 显示了 bash 中常见的运算符。

表 2-1. bash 中的常见运算符

运算符描述
==比较字符串是否相等
!=否定或不等于,适用于字符串
比较字符串排序顺序,大于
<比较字符串排序顺序,小于
-eq等于,适用于数字
-ne不等于,适用于数字
-lt小于,适用于数字
-gt大于,适用于数字
-le小于或等于,适用于数字
-ge大于或等于,适用于数字

bash 还包括几个用于测试文件和目录属性的运算符,如表 2-2 所示。这些运算符在 bash 脚本编写的上下文中非常有用,因为 bash 脚本经常用于解决文件系统相关的问题。

表 2-2. bash 中的选择运算符

运算符描述
-d测试文件是否为目录
-e测试文件是否存在
-f测试文件是否为普通文件,而不是目录或特殊文件
-r评估运行测试的用户是否对文件有读取权限
-w评估运行测试的用户是否对文件有写入权限
-x评估运行测试的用户是否对文件有执行权限

还有许多其他文件测试,详细描述可参阅《高级 bash 脚本指南》。

到目前为止,条件语句的讨论仅限于 if 语句。然而,bash 和其他语言还提供了一种通过 else 条件执行代码的方法,适用于测试失败的情况。一个现实世界的例子:如果温度高于 85 度(华氏度),则穿短裤,否则穿裤子。

else 条件提供了在主要测试失败时将执行的代码。但如果你需要执行多个测试怎么办?你可以使用 elif 语句执行另一个测试。继续天气的例子:如果温度高于 85 度,则穿短裤,否则如果温度高于 50 度,则穿裤子,否则穿裤子并带上外套。代码如下:

if [ $temperature -gt 85 ]
then
    echo "Wear shorts"
elif [ $temperature -gt 50 ]
then
    echo "Wear pants"
else
    echo "Wear pants and bring a jacket"

经过一些 if/elif 组合后,代码可能会变得有些繁琐,不易排查和维护。bash 和其他语言提供了 case 语句,可以高效地评估多个选项,并根据评估结果选择执行路径。本书不会进一步讨论 casecase/switch。有关更多信息,请参阅《初学者 bash 指南》。

循环

到目前为止,我已经介绍了如何在程序中使用变量存储数据,以及如何根据这些变量的内容或其他测试在代码中做出决策。接下来是循环,即多次执行相同的操作。

大多数语言至少包含两种循环方式,视语言而定,还可以使用几种其他方式。bash 包括 for 循环和 while 循环以及与其密切相关的 until 循环。

for 循环会遍历一段代码块若干次,这个次数取决于程序的需求。for 循环通常用于遍历一个项目列表,对列表中的每个项目执行一个操作。这里是一个 for 循环的示例,它使用一个文件列表并在文件是常规文件而不是目录或特殊字符设备时输出信息:

files=$( ls )
for file in $files
do
  if [ -f "$file" ]
  then
    echo "$file is a regular file"
  fi
done

在示例代码中,有一个我们尚未介绍的新元素。代码的第一行将 ls 命令的输出赋值给脚本中的一个变量。还有其他捕获输出的方法,包括将命令括在反引号或 ` 中(也称为反勾号)。如图所示的语法 $( ) 在输出中可能涉及空格时(如 ls 收集到的文件名)特别有用。

while 循环仅在条件为真时遍历代码块。如果条件一开始就不为真,那么 while 循环将永远不会执行代码。until 循环类似于 while 循环,只不过它仅在条件为假时执行代码。如果条件一开始为真,则 until 循环内的代码将永远不会执行。

注意 你可能听说过 DRY 原则,或称“不要重复自己”(Don’t Repeat Yourself)。DRY 原则旨在提醒避免在程序的多个位置重复代码或相同的代码块。虽然与循环关系不大,但当违反 DRY 原则时,代码将变得更难维护。

考虑一个计算销售税的代码示例。可能需要在代码中的多个位置执行相同的计算。如果需要更改税收计算方式,负责更改的开发人员需要找到所有存在相同代码的位置。

列表和数组

在前一节中,使用 ls 命令收集了一个文件列表。列表是另一种常用的编程构造。列表也可能被称为数组,有时你可能听到列表被称为字典或其他类似术语。列表、数组和字典有所不同,但这些区别超出了本书的范围。可以将列表视为 bash 中数组的简单形式,前一节显示了列表的常见创建和使用方法。bash 中的数组可以是按数字索引的,也可以按字符串索引,也称为关联数组或字典。当你阅读本书后面的更多示例时,数组和列表的使用将变得更加明显。

总结

本章概述了与个人和组织迈向 DevSecOps 相关的几个概念。内容的覆盖故意比较广泛,因为在短短 25 页甚至 250 页内全面覆盖所有必要的基础概念是不可能的。关于本章各个子部分的整本书籍早已存在。因此,我的期望是,至少能让你对 DevSecOps 从业者所需的广泛知识基础有一些了解。就像学习演奏乐器一样,你练习得越多,就会在开发、安全和运维这三个领域中增加所需技能的深度和广度。

本章以倡导学习命令行开篇,无论使用何种操作系统。未来几年,学习命令行,尤其是基于 Unix 的命令行(如 Linux 或 macOS),将帮助你自动化任务并在日常工作中提高效率。接下来,本章讨论了端口和协议,并介绍了用于表示通信的 OSI 和互联网模型。协议部分包括 DNS 和 HTTP,特别关注 DNS 和 HTTP 作为基础协议的重要性,虽然它们理应能正常工作,但有时却可能出现问题。

接着,我介绍了机密性、完整性和可用性 (CIA) 的安全三元组——虽然有关安全的更多内容将在第 3 章中讨论。最后,在编程构造部分,目标只是向读者介绍编程中的一些关键术语。考虑到 bash 脚本在 DevSecOps 中的核心地位,示例大多集中在 bash 上,但许多其他现代编程语言中也存在类似的编程构造。

第 3 章的重点是基础安全知识。该章涵盖了基本的 CIA 三元组及相关的安全概念,目的是将这些概念融入整个开发生命周期。该章还展示了使用 OWASP 的一些实际工具。