利用GitHub的安全功能保护软件供应链

268 阅读12分钟

**TL;DR:**现代软件交付中最关键的方面之一是安全。在开源项目的时代,要控制每一个漏洞,确保我们的解决方案不使用有严重漏洞的软件包,是具有挑战性的,也是不容易的。今天对供应链安全的威胁是未打补丁的软件。在这篇文章中,我们将学习如何利用GitHub的安全功能来提高代码安全。

软件供应链中的安全角色

现在的应用实施看起来与几年前略有不同。开源软件已经成为一种标准。在不同类型的项目中使用开源库是很正常的事情,我们不必从头开始编写应用程序的每个部分。

当使用一些开源代码或库时,值得不要忘记安全方面的问题。行业数据表明,99%的代码库包含开源代码。这是一个很大的数字!有了这个事实,我们必须记住与漏洞和错误有关的危险,这可能会导致不良后果。我们可以提供许多例子,其中使用了一个开源库,但后来发现了严重的漏洞,最后,在保持安全的正确水平上出现了问题。

在讨论安全在软件供应链中的作用之前,我们必须先弄清楚它是什么。软件供应链是指任何进入或影响源代码的东西,从开发到持续集成和交付管道,直到它被部署到生产和广泛使用。有例子吗?软件供应链包括以下信息:谁写了一个特定的代码片段,哪些工具被用于应用安全扫描,什么是用于托管我们的解决方案的基础设施,或使用什么依赖。

当一个解决方案被开发时,安全部分往往被遗漏,或者被作为软件供应链整个过程中的最后一步。如今,对供应链安全的最大威胁之一是未打补丁的软件。这就是为什么我们不应该把安全评估作为软件供应链的最后一步。安全应该是软件开发的一个持续的部分,以确保任何潜在的漏洞可以被迅速缓解。

关注每一个安全方面可能是一个挑战,特别是对于那些专注于代码实现而不是扫描和搜索漏洞的开发人员。另外,立即向开发人员提供安全反馈是很重要的,这样可以尽快消除任何潜在的漏洞。

在下文中,我们将重点介绍GitHub上的安全功能,以确保我们的软件供应链更加安全。

示例应用程序

为了展示一些实际的安全功能,你可以使用这个GitHub资源库中的示例应用程序。这个资源库包含一个简单的网页游戏,在.NET 5中写成一个ASP .NET Core MVC应用程序。在这篇文章中,我们不会专注于实现细节。我们的目标是发现和理解GitHub上的不同安全功能,并看到安全扫描的实际效果。有了这个资源库,你可以自己尝试安全功能。使用该 for-security-features-testing分支作为起点,在你的机器上运行以下命令。

git clone -b for-security-features-testing https://github.com/auth0-blog/Globomantics-Games.git

保持仓库安全的 GitHub 安全功能

GitHub 提供了一些内置工具,使我们的源代码安全保持在合适的水平。不过,我们也可以整合第三方工具。让我们先来讨论一下GitHub的内置安全功能。

仓库依赖关系图

在我们的项目中使用开源库时,有时很难跟踪所有的依赖关系。当然,我们可以打开我们的包管理器(如NuGet),看到所有添加到项目中的开源包。但是对我们引用的包所使用的其他包有更深入的了解呢?令人困惑和具有挑战性,对吗?这就是GitHub仓库的依赖关系图可以提供的帮助。通过依赖关系图,我们可以递归地追踪项目中使用的所有依赖关系。

我们可以通过打开 GitHub 仓库的主页,在Insights标签下找到依赖关系图,如下图所示。

GitHub dependency graph

依赖关系图在 GitHub 上的每个仓库都是默认启用的。它可以扫描常见的软件包清单,比如。

  • package.json
  • requirements.txt
  • packages.config
  • .csproj文件

自动依赖性扫描

一旦我们知道项目中使用了哪些依赖,最好能自动扫描它们并检测潜在的漏洞。这就是自动依赖性扫描的作用。GitHub提供了自动化的依赖性警报(使用Dependabot),观察依赖性图表。它将项目依赖的目标版本与GitHub咨询数据库中汇总的已知漏洞列表中的版本进行交叉对比。

当检测到一个风险时,项目就会收到警告,如下图所示。

GitHub dependency scanning

这还不够。为了方便开发者,一旦在使用的某个库中检测到漏洞,就会自动创建一个拉动请求,其中包含该库的升级版本。

我们可以通过访问GitHub上设置标签下的安全与分析部分,启用Dependabot警报Dependabot安全更新功能,如下图所示。

GitHub security updates

秘密扫描

在任何源代码仓库中存储秘密和凭证都是一种不好的做法。当向公共仓库推送时,GitHub会扫描提交内容中的秘密和凭证。

更重要的是,GitHub 会检测来自不同供应商的已知秘密类型,如 Adobe、Microsoft Azure、Atlassian、Dropbox 等。

一旦检测到秘密,仓库所有者就会收到警报。下面的图片展示了扫描的流程。

GitHub secret scanning

[来源。GitHub]

一旦新推送的源代码中嵌入了秘密,GitHub 就会应用重码扫描来验证是否有任何已知模式的秘密。如果检测到秘密,GitHub会将发现的秘密值发送到服务提供商的验证端点。如果验证成功,该秘密就会被撤销,并向源代码仓库所有者发送电子邮件提醒。

秘密扫描适用于所有公共仓库和启用了GitHub高级安全的组织所拥有的私有仓库。你可以在这里阅读更多关于秘密扫描合作伙伴计划的信息。

安全政策

自动扫描是很有帮助的,但是为其他开发者和使用/贡献给我们项目的人提供一个报告发现的漏洞的方法也很重要,一般来说。我们可以通过提供一个 SECURITY.md文件,在版本库的root,docs, 或 .github文件夹中。这个文件提供了如何报告漏洞和联系版本库所有者或哪些版本支持安全更新的指导。通过这个文件提供指导,可以加快解决关键问题的速度。当有人在我们的版本库中创建一个问题时,他们会看到一个指向我们项目安全策略的链接。

下图是你可以在我们的样本项目中找到的文件。

GitHub security policy

按照惯例,这个 SECURITY.md文件位于我们 GitHub 仓库的根目录下。安全策略没有默认的模板。然而,我们至少应该尝试放上我们的联系信息,并解释如何对报告的安全问题进行核实。安全策略文件是GitHub上默认的社区健康文件的一部分,所以这意味着我们必须准确地以这种方式命名它。 SECURITY.md.

自动代码扫描

通过GitHub Actions,我们可以启用自动代码扫描,这样每次进行新的合并时,我们的代码都会被扫描到。启用代码扫描后,我们可以分析并发现GitHub仓库中托管的代码的安全漏洞和错误。

GitHub提供了一个内置的代码扫描工具,叫做CodeQL,当然我们也可以整合其他第三方工具。添加代码扫描对于帮助防止开发人员在源代码中引入任何新的安全问题很有帮助。代码扫描也可以帮助提高我们的代码质量。

在高层次上,CodeQL是一个分析引擎,被开发人员用来自动进行安全检查,也被安全研究人员用来进行变体分析。在下一节中,我们将发现如何用CodeQL启用代码扫描,以及有哪些配置选项可用。要了解更多关于CodeQL的信息,请查看官方文档

用CodeQL进行版本库代码扫描

当使用CodeQL时,我们的源代码被视为数据。安全漏洞、bug和其他错误被模拟成可以针对从源代码中提取的数据库执行的查询。在CodeQL分析中有三个主要步骤。

  1. 准备,通过创建一个CodeQL数据库来准备代码。
  2. 分析,针对数据库运行CodeQL查询。
  3. 解释,解释查询结果以检查是否发现任何漏洞或代码错误。

CodeQL可以用来扫描许多不同的语言,如C++、C#、Java或Phyton。当CodeQL扫描完成后,我们可以使用多个查询。为了使它更容易,我们可以使用查询套件。CodeQL查询套件是 .qls文件,允许我们将多个查询传递给CodeQL,而不必单独指定每个查询文件的路径。

在下文中,我们将看到如何使用安全和质量查询套件来检测源代码中的漏洞和安全问题。

要启用CodeQL扫描,我们打开安全部分,选择代码扫描警报。然后我们点击CodeQL分析项目内的设置此工作流按钮。

GitHub security policy

现在,将工作流程的默认代码替换为以下内容。

name: "Build and scan project with CodeQL"

on:
  pull_request:
    branches: [ main ]

jobs:
  analyze:
    name: analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'csharp' ]

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - name: Initialize CodeQL
      uses: github/codeql-action/init@v1
      with:
        languages: ${{ matrix.language }}
        config-file: .github/workflows/codeql/codeql-config.yml

    - name: Autobuild
      uses: github/codeql-action/autobuild@v1

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v1

一旦替换了现有的代码,我们可以提交 codeql-analysis.yml生成的文件。现在,让我们来讨论它的结构。

首先,我们可以决定何时运行扫描。在我们的例子中,一旦创建了一个拉动请求,将改动合并到main 分支,就会执行扫描。在jobs 部分,我们声明了一个运行者环境,由 runs-on.通过在作业定义中指定权限,你可以为每个作业配置一组不同的权限。 GITHUB_TOKEN如果需要的话,你可以为每个作业配置不同的权限。你可以在官方文档中阅读更多关于作业权限的内容。

strategy 部分,我们可以声明在项目中应该对哪些语言进行扫描。在我们的例子中,我们想扫描C#语言。我们在matrix 部分声明这些语言。随着 fail-fast设置为 false,我们可以避免在对其中一种声明的语言扫描失败时破坏整个工作流程。

steps 部分,有整个过程的步骤。

  1. Checkout repository, 签出项目的代码进行扫描。
  2. 初始化CodeQL,以初始化CodeQL扫描在matrix 部分声明的语言。在该 config-file部分,我们可以提供一个路径到 codeql-config.yml文件的路径,在那里我们声明在扫描过程中使用的查询套件。在这种情况下,我们使用 security-and-quality查询套件。该 codeql-config.yml文件不是强制性的,但它有助于保持CodeQL扫描脚本的可读性。我们还可以用这个文件来禁用默认的查询,并指定在分析过程中要扫描的目录。
  3. 自动构建,构建源代码。
  4. 执行CodeQL分析,进行分析并发布报告。

以下是文件的内容 codeql-config.yml文件的内容。

name: "Security and Quality"

queries:
  - name: Security and Quality
    uses: security-and-quality

一旦扫描完成,我们可以在安全标签下的代码扫描警报部分查看报告。

CodeQL Analysis result

在上面的报告中,我们可以看到一个建议,即先检查变量是否为null,然后尝试访问它,而不是捕捉一个NullReferenceException 。通过CodeQL,我们可以发现许多不同的安全问题,并获得关于如何提高代码质量和消除潜在错误的提示。我建议通过阅读官方文档来了解更多关于CodeQL的分析。

许可证合规性管理

当谈及开源软件时,重要的是要记住许可证合规性。有许多不同类型的许可证,在我们的项目中使用一个开源库之前,我们必须仔细验证它们。最常用的开源许可证是。

  • GNU通用公共许可证(GPL)
  • 阿帕奇许可证
  • MIT许可证
  • GNU Less General Public License (LGPL)

例如,GPL是一个自由复制的许可证。使用自由复制许可证意味着包含开放源码自由复制许可证的软件产品必须以同样的自由复制许可证发布。因此,如果我们在软件中使用GPL许可的库,我们就不能用MIT许可发布,例如。

MIT是一个允许性的许可证。在MIT许可下的软件可以不受限制地使用和修改--只要在其上添加一份原始的MIT许可和版权声明。

所以,在这种情况下,我们必须要小心,因为当我们想把我们的软件产品商业化时,这可能会有问题。

GitHub默认不提供许可证扫描功能。可以使用第三方工具来设置许可证扫描。例如,Snyk就是这些第三方工具中的一个。通过Snyk开源许可证合规管理,我们可以扫描我们的项目,验证我们使用的库中使用了哪些许可证。

Snyk的许可证扫描在付费计划中可用,但也有14天的试用版。一旦我们使用GitHub账户登录到app.snyk.io,我们就可以选择我们想扫描的项目。在我们的案例中,让我们继续使用Snyk,而不授予GitHub上的仓库直接权限。我们稍后会看到如何将GitHub连接到Snyk的API。所以,让我们点击继续使用而不授予权限,如下图所示。

Snyk setup

在下一节中,我们提供 GitHub 上公共仓库的 URL。在我们的例子中,它是github.com/auth0-blog/…。然后,让我们点击导入1个仓库的按钮。登录后,我们可以点击开始免费试用按钮来激活14天的试用。

在这一点上,让我们点击Snyk网站右上角的齿轮图标。在左边的栏中,我们必须切换到许可证。在这里我们可以看到一旦应用Snyk许可证扫描,在我们的项目中发现的所有可用许可证的列表。

Snyk license scanning

正如我们在上图中看到的,我们可以为特定的许可证类型分配不同的严重程度(None,Low,Medium, 或High )。让我们选择 Apache-2.0列表中的许可证。我们可以添加说明,当在我们的项目中发现这种类型的许可证时,应该确切地验证什么。注意,严重程度被设置为Medium

Apache license in Snyk

现在,为了在我们的代码上启用Snyk扫描,我们需要从Synk获得一个访问令牌,并将其存储在GitHub的Secrets部分。所以,让我们点击右上角的头像,从列表中选择账户设置。在Auth Token部分,让我们点击Click to show 来显示该令牌。最后,我们复制该令牌,以便在负责许可证扫描的 GitHub Actions 工作流中使用它。

回到 GitHub 上,我们点击Secrets标签,选择Secrets。我们使用新仓库秘密按钮,粘贴从 Snyk 获得的令牌。一旦秘密被保存,它应该显示在列表中。

Snyk token configuration on GitHub

现在是时候使用 GitHub Actions 添加 Snyk 扫描工作流程了。在 .github/workflows文件夹下(也就是我们把 codeql-analysis.yml文件的地方),我们必须把内容如下的 snyk-license-scanning.yml文件,其内容如下。

name: "Build and scan project with Snyk"

on:
  push:
    branches: [ main ]

jobs:
  build-and-scan:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies
      run: dotnet restore ./src/web-game/Globomantics.sln
    - name: Build
      run: dotnet build ./src/web-game/Globomantics.sln --no-restore
    - name: Test
      run: dotnet test ./src/web-game/Globomantics.sln --no-build --verbosity normal


    - name: Run Snyk to check for vulnerabilities
      uses: snyk/actions/dotnet@master
      continue-on-error: true # To make sure that SARIF upload gets called
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      with:
        args: --sarif-file-output=snyk.sarif --file=./src/web-game/Globomantics.sln --severity-threshold=medium


    - name: Upload result to GitHub Code Scanning
      uses: github/codeql-action/upload-sarif@v1
      with:
        sarif_file: snyk.sarif

我们来讨论一下它的结构。我们想在推送到main 分支时执行扫描。在jobs 部分,我们声明了一个运行环境,指定为 runs-on.在steps 部分,有几个必要的步骤。

  1. 签出版本库,签出项目的代码进行扫描。
  2. 设置.NET,包括用dotnet restore 命令恢复依赖关系,然后dotnet build 来构建项目,如果解决方案中存在测试,dotnet test 来运行测试。
  3. 运行Snyk来检查漏洞,它与Snyk的API进行通信以应用代码扫描。注意,我们必须将 SNYK_TOKEN用我们在Secrets 部分中的值。还有一些参数。
    • 该参数提供了包含扫描报告的文件名称。 --sarif-file-output=snyk.sarif参数提供了包含扫描报告的文件名。
    • 参数提供了包含扫描报告的文件名。 --file=./src/web-game/Globomantics.sln参数提供了项目/解决方案的路径。
    • 该参数用于报告严重程度为 及以上的警报。 --severity-threshold=medium- 参数用于报告严重程度为Medium 及以上的警报。
  4. 将结果上传到GitHub代码扫描,它负责上传扫描结果,这样我们就可以在安全标签下看到它。

一旦我们推送任何修改到main 分支,扫描就会被触发。让我们来看看下面显示的扫描报告。

License scanning report

正如我们在上面看到的,在 Apache-2.0许可证是在我们使用的一个库中发现的:Serilog 。一旦我们点击警报,我们可以看到如下所示的细节。

License scanning details

我们可以看到,Serilog 库使用 Apache-2.0许可证,并且我们使用NuGet包管理器将这个包包含在 Globomantics.CloudGame我们使用NuGet包管理器将这个包包含在项目中。我们看到这个警报的原因是,我们把参数设置为。 --severity-threshold``Medium在这种情况下,只要我们的项目中有一个库使用了我们在Snyk配置中设置为Medium 的许可证,我们就会收到警告。

结论

在这篇文章中,我们发现了如何启用和使用GitHub的安全功能来提高代码安全和软件供应链。我们学习了如何启用自动依赖扫描和更新,使用 CodeQL 启用扫描,以及使用 Snyk 验证许可证合规性。

如果你想了解更多关于本文所学的内容,请查看以下文档。

除此之外:用Auth0保证ASP.NET Core的安全

用Auth0保证ASP.NET Core应用程序的安全很容易,并带来了很多伟大的功能。有了Auth0,你只需要写几行代码就可以得到一个坚实的身份管理解决方案单点登录、对社会身份提供者(如Facebook、GitHub、Twitter等)的支持,以及对企业身份提供者(如活动目录、LDAP、SAML、自定义等)的支持。

在ASP.NET Core上,你需要在你的Auth0管理仪表板上创建一个API,并在你的代码上改变一些东西。要创建一个API,你需要注册一个免费的Auth0账户。之后,你需要进入仪表板的API部分,点击 "创建API"。在显示的对话框中,你可以将你的API的名称设置为 "书籍",标识符"http://books.mycompany.com",并将签名算法设为 "RS256"。

Creating API on Auth0

之后,你必须在调用的 services.AddAuthentication()ConfigureServices()方法,如下所示:Startup 类。

string authority = $"https://{Configuration["Auth0:Domain"]}/";
string audience = Configuration["Auth0:Audience"];

services.AddAuthentication(options =>
{
  options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
  options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
  options.Authority = authority;
  options.Audience = audience;
});

Configure()``Startup 方法的主体中,你还需要添加一个调用到 app.UseAuthentication()app.UseAuthorization()如下图所示。

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

请确保你按照上面的顺序调用这些方法。这是至关重要的,这样一切才能正常工作。

最后,将以下元素添加到 appsettings.json配置文件。

{
  "Logging": {
    // ...
  },
  "Auth0": {
    "Domain": "YOUR_DOMAIN",
    "Audience": "YOUR_AUDIENCE"
  }
}

注意:替换占位符 YOUR_DOMAINYOUR_AUDIENCE替换为你在创建Auth0账户时指定的域的实际值和你分配给你的API的标识符