├── .dockerignore ├── .editorconfig ├── .github └── workflows │ ├── CI.yml │ ├── PreRelease.yml │ └── Release.yml ├── .gitignore ├── GitVersion.yml ├── LICENSE ├── README.md ├── SampleProjects ├── VulnerableConsoleApp │ ├── Program.cs │ └── VulnerableConsoleApp.csproj ├── VulnerableRunTimeWebApp │ ├── Program.cs │ └── VulnerableRunTimeWebApp.csproj └── VulnerableSolution.sln ├── build.cake ├── dotnet-retire.sln ├── gitprune.sh ├── images └── logo.png ├── install.cmd ├── src ├── RetireNet.Runtimes.BackgroundServices │ ├── RetireNet.Runtimes.BackgroundServices.csproj │ ├── RetireRuntimeBackgroundService.cs │ ├── RetireRuntimeBackgroundServiceOptions.cs │ └── ServiceCollectionExtensions.cs ├── RetireNet.Runtimes.Core │ ├── Clients │ │ ├── Models │ │ │ ├── AppRunTimeDetails.cs │ │ │ ├── CVE.cs │ │ │ ├── Channel.cs │ │ │ ├── Release.cs │ │ │ └── Report.cs │ │ └── ReleaseMetadataClient.cs │ ├── HttpClients │ │ ├── Models │ │ │ ├── Channels │ │ │ │ ├── Channel.cs │ │ │ │ ├── Release.cs │ │ │ │ └── Runtime.cs │ │ │ └── Index │ │ │ │ ├── Channel.cs │ │ │ │ └── ReleaseIndex.cs │ │ └── ReleaseMetadataHttpClient.cs │ ├── ReportGenerator.cs │ └── RetireNet.Runtimes.Core.csproj └── RetireNet.Runtimes.Middleware │ ├── AppBuilderExtensions.cs │ ├── RetireNet.Runtimes.Middleware.csproj │ └── RetireRunTimeMiddleware.cs ├── test └── RetireNet.Runtimes.Core.Tests │ ├── RetireNet.Runtimes.Core.Tests.csproj │ └── RuntimeReportIntegrationTests.cs └── tools └── packages.config /.dockerignore: -------------------------------------------------------------------------------- 1 | **/Obj/ 2 | **/obj/ 3 | **/bin/ 4 | **/Bin/ 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.cs] 12 | indent_size = 4 13 | csharp_style_namespace_declarations = file_scoped:warning 14 | 15 | [*.yml] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ubuntu-16_04: 7 | name: ubuntu 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - uses: actions/setup-dotnet@v1 12 | with: 13 | dotnet-version: | 14 | 5.0.x 15 | 6.0.x 16 | - name: Test 17 | run: dotnet test --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover --logger "GitHubActions;report-warnings=false" 18 | -------------------------------------------------------------------------------- /.github/workflows/PreRelease.yml: -------------------------------------------------------------------------------- 1 | name: PreRelease 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - run: echo "ACTIONS_ALLOW_UNSECURE_COMMANDS=true" >> $GITHUB_ENV 15 | - name: Install GitVersion 16 | uses: gittools/actions/gitversion/setup@v0.9.7 17 | with: 18 | versionSpec: "5.x" 19 | - name: Determine Version 20 | id: gitversion 21 | uses: gittools/actions/gitversion/execute@v0.9.7 22 | with: 23 | useConfigFile: true 24 | - name: Setup .NET 6 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: "6.0.x" 28 | - name: Pack 29 | run: dotnet pack /p:Version=${{ steps.gitversion.outputs.NuGetVersionV2 }}-${{ steps.gitversion.outputs.ShortSha }} /p:InformationalVersion=${{ steps.gitversion.outputs.informationalVersion }} /p:PackageReleaseNotes="https://github.com/$GITHUB_REPOSITORY/releases/tag/${{ steps.gitversion.outputs.NuGetVersionV2 }}" -o ./releases 30 | - name: Publish 31 | run: dotnet nuget push ./releases/**/*.nupkg -k=${{ secrets.NUGETORGAPIKEY }} -s=nuget.org 32 | -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | - run: echo "ACTIONS_ALLOW_UNSECURE_COMMANDS=true" >> $GITHUB_ENV 15 | - name: Install GitVersion 16 | uses: gittools/actions/gitversion/setup@v0.9.7 17 | with: 18 | versionSpec: "5.x" 19 | - name: Determine Version 20 | id: gitversion 21 | uses: gittools/actions/gitversion/execute@v0.9.7 22 | with: 23 | useConfigFile: true 24 | - name: Setup .NET 6 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: "6.0.x" 28 | - name: Pack 29 | run: dotnet pack /p:Version=${{ steps.gitversion.outputs.majorMinorPatch }} /p:InformationalVersion=${{ steps.gitversion.outputs.informationalVersion }} /p:PackageReleaseNotes="https://github.com/$GITHUB_REPOSITORY/releases/tag/${{ steps.gitversion.outputs.majorMinorPatch }}" -o ./releases 30 | - name: Publish 31 | run: dotnet nuget push ./releases/**/*.nupkg -k=${{ secrets.NUGETORGAPIKEY }} -s=nuget.org 32 | - name: Generate CHANGELOG.md 33 | id: releasenotes 34 | run: | 35 | gh api repos/$GITHUB_REPOSITORY/releases/generate-notes \ 36 | -f tag_name="${{ steps.gitversion.outputs.majorMinorPatch }}" \ 37 | -q .body > CHANGELOG.md 38 | echo -e "\n\n" >> CHANGELOG.md 39 | git log $(git describe --tags --abbrev=0)..HEAD --oneline >> CHANGELOG.md 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | - name: Create Release 43 | id: create_release 44 | uses: actions/create-release@v1 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | with: 48 | tag_name: ${{ steps.gitversion.outputs.majorMinorPatch }} 49 | release_name: Release ${{ steps.gitversion.outputs.majorMinorPatch }} 50 | body_path: CHANGELOG.md 51 | draft: false 52 | prerelease: false 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | tools/** 279 | !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | 290 | .DS_Store 291 | deploy/ 292 | output/ 293 | 294 | !**/RetireNet.Packages.Tool.Tests/TestFiles/** 295 | **opencover.xml 296 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | master: 3 | regex: ^master$ 4 | mode: ContinuousDeployment 5 | tag: "" 6 | feature: 7 | regex: ^features?[/-]|(?!^master$|^(hotfix|bugfix)(es)?[/-]|^support[/-]|(^(pull|pull\-requests|pr)[/-]))(^.*$) 8 | mode: ContinuousDeployment 9 | tag: "feature" 10 | pull-request: 11 | tag: "pull" 12 | mode: ContinuousDeployment 13 | hotfix: 14 | regex: ^(hotfix|bugfix)(es)?[/-] 15 | mode: ContinuousDeployment 16 | increment: Inherit 17 | tag: "bug" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 RetireNet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Build status 2 | 3 | [![Build](https://github.com/retirenet/dotnet-retire/workflows/CI/badge.svg)](https://github.com/retirenet/dotnet-retire/actions) 4 | 5 | # Components 6 | 7 | * [![NuGet](https://img.shields.io/nuget/v/RetireNet.Runtimes.Middleware.svg)](https://www.nuget.org/packages/RetireNet.Runtimes.Middleware/) 8 | [![NuGet](https://img.shields.io/nuget/dt/RetireNet.Runtimes.Middleware.svg)](https://www.nuget.org/packages/RetireNet.Runtimes.Middleware/) `RetireNet.Runtimes.Middleware` 9 | 10 | * [![NuGet](https://img.shields.io/nuget/v/RetireNet.Runtimes.BackgroundServices.svg)](https://www.nuget.org/packages/RetireNet.Runtimes.BackgroundServices/) 11 | [![NuGet](https://img.shields.io/nuget/dt/RetireNet.Runtimes.BackgroundServices.svg)](https://www.nuget.org/packages/RetireNet.Runtimes.BackgroundServices/) `RetireNet.Runtimes.BackgroundServices` 12 | 13 | 14 | 15 | ## ~~dotnet-retire~~ 16 | ❗️DEPRECATED❗️ 17 | 18 | See [this issue for other solutions](https://github.com/RetireNet/dotnet-retire/issues/75). 19 | 20 | 21 | ## RetireNet.Runtimes.Middleware 22 | We cannot detect the runtime of the app at build time, so to report use of vulnerable runtimes the app itself, the host itself can provide us reports 23 | 24 | ### Install 25 | ``` 26 | $ dotnet add package RetireNet.Runtimes.Middleware 27 | ``` 28 | 29 | ### Usage 30 | 31 | Add it to your ASP.NET Core pipeline on your preferred path: 32 | 33 | ```csharp 34 | app.Map("/report", a => a.UseRuntimeVulnerabilityReport()); 35 | ``` 36 | 37 | ### What does it do? 38 | It will fetch the releases listed in the official metadata API provided by Microsoft, and check if your app is running on a runtime with known CVEs. 39 | 40 | Metadata endpoint used: https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json 41 | 42 | 43 | ### Sample output 44 | 45 | An app running on the vulnerable 2.1.11 runtime on macOS: 46 | ```json 47 | { 48 | "isVulnerable": true, 49 | "appRuntimeDetails": { 50 | "os": "OSX", 51 | "osPlatform": "Darwin 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64", 52 | "osArchitecture": "X64", 53 | "osBits": "64", 54 | "appTargetFramework": ".NETCoreApp,Version=v2.1", 55 | "appRuntimeVersion": "2.1.11", 56 | "appBits": "64" 57 | }, 58 | "securityRelease": { 59 | "runtimeVersion": "2.1.13", 60 | "cvEs": [ 61 | { 62 | "cve-id": " CVE-2018-8269", 63 | "cve-url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-8269" 64 | }, 65 | { 66 | "cve-id": " CVE-2019-1301", 67 | "cve-url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1301" 68 | }, 69 | { 70 | "cve-id": " CVE-2019-1302", 71 | "cve-url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1302" 72 | } 73 | ] 74 | } 75 | } 76 | ``` 77 | 78 | ## RetireNet.Runtimes.BackgroundServices 79 | This is the same report as for the middleware, only logging it using the configured `ILogger` as a _WARN_ log statment. 80 | 81 | ### Install 82 | ``` 83 | $ dotnet add package RetireNet.Runtimes.BackgroundServices 84 | ``` 85 | 86 | ### Usage 87 | 88 | Register it into the container, and provide it a interval in milliseconds how often you would like the check to execute. 89 | 90 | ```csharp 91 | services.AddRetireRuntimeHostedService(c => c.CheckInterval = 60000) 92 | ``` 93 | 94 | ### What does it do? 95 | The same as for the middleware endpoint. 96 | 97 | 98 | ### Sample output 99 | 100 | An app running on the vulnerable 2.1.11 runtime on macOS, using the `ConsoleLogger`: 101 | ``` 102 | warn: RetireNet.Runtimes.BackgroundServices.RetireRuntimeBackgroundService[0] 103 | Running on vulnerable runtime 2.1.11. Security release 2.1.13 104 | ``` 105 | -------------------------------------------------------------------------------- /SampleProjects/VulnerableConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace VulnerableConsoleApp 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | var host = new HostBuilder() 12 | .ConfigureLogging(c => c.AddDebug().AddConsole()) 13 | .ConfigureServices(services => services.AddRetireRuntimeHostedService(c => c.CheckInterval = 5000)) 14 | .Build(); 15 | host.Run(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SampleProjects/VulnerableConsoleApp/VulnerableConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SampleProjects/VulnerableRunTimeWebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Formatting.Compact; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | builder.Services.AddRetireRuntimeHostedService(o => o.CheckInterval = 10000); 6 | builder.WebHost.UseSerilog((hostingContext, loggerConfiguration) => 7 | loggerConfiguration 8 | .MinimumLevel.Debug() 9 | .WriteTo.Console(new CompactJsonFormatter())); 10 | 11 | var app = builder.Build(); 12 | app.UseRuntimeVulnerabilityReport(); 13 | await app.RunAsync(); 14 | 15 | -------------------------------------------------------------------------------- /SampleProjects/VulnerableRunTimeWebApp/VulnerableRunTimeWebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp6.0 5 | preview 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /SampleProjects/VulnerableSolution.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VulnerableApp", "VulnerableApp\VulnerableApp.csproj", "{8FD5683C-9718-4C00-97A2-D855F0264A2B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VulnerableConsoleApp", "VulnerableConsoleApp\VulnerableConsoleApp.csproj", "{9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Debug|x64.Build.0 = Debug|Any CPU 27 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Debug|x86.Build.0 = Debug|Any CPU 29 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Release|x64.ActiveCfg = Release|Any CPU 32 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Release|x64.Build.0 = Release|Any CPU 33 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Release|x86.ActiveCfg = Release|Any CPU 34 | {8FD5683C-9718-4C00-97A2-D855F0264A2B}.Release|x86.Build.0 = Release|Any CPU 35 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Debug|x64.Build.0 = Debug|Any CPU 39 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Debug|x86.Build.0 = Debug|Any CPU 41 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Release|x64.ActiveCfg = Release|Any CPU 44 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Release|x64.Build.0 = Release|Any CPU 45 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Release|x86.ActiveCfg = Release|Any CPU 46 | {9AA72A7B-EEF8-4BBA-A8E5-C6BE03F4BD03}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | var target = Argument("target", "Pack"); 2 | var configuration = Argument("configuration", "Release"); 3 | 4 | var dotnetRetireProjName = "RetireNet.Packages.Tool"; 5 | var dotnetRetirePackageId = "dotnet-retire"; 6 | var dotnetRetireProj= $"./src/{dotnetRetireProjName}/{dotnetRetireProjName}.csproj"; 7 | var dotnetRetireVersion = "5.0.0"; 8 | 9 | var dotnetMiddlewareName = "RetireNet.Runtimes.Middleware"; 10 | var dotnetMiddlewarePackageId = "RetireNet.Runtimes.Middleware"; 11 | var dotnetMiddlewareProj= $"./src/{dotnetMiddlewareName}/{dotnetMiddlewareName}.csproj"; 12 | 13 | var dotnetBackgroundServiceName = "RetireNet.Runtimes.BackgroundServices"; 14 | var dotnetBackgroundServicePackageId = "RetireNet.Runtimes.BackgroundServices"; 15 | var dotnetBackgroundServiceProj = $"./src/{dotnetBackgroundServiceName}/{dotnetBackgroundServiceName}.csproj"; 16 | 17 | var runtimeCheckersVersion = "5.0.0"; 18 | 19 | var outputDir = "./output"; 20 | 21 | var sln = "dotnet-retire.sln"; 22 | 23 | Task("Build") 24 | .Does(() => { 25 | DotNetCoreBuild(sln, new DotNetCoreBuildSettings { 26 | Configuration = "Release" }); 27 | }); 28 | 29 | Task("Test") 30 | .IsDependentOn("Build") 31 | .Does(() => { 32 | var projectFiles = GetFiles("./test/**/*.csproj"); 33 | foreach(var file in projectFiles) 34 | { 35 | DotNetCoreTest(file.FullPath, new DotNetCoreTestSettings { 36 | ArgumentCustomization = args=>args.Append("/p:CollectCoverage=true /p:CoverletOutputFormat=opencover") 37 | }); 38 | } 39 | }); 40 | 41 | Task("Pack") 42 | .IsDependentOn("Test") 43 | .Does(() => { 44 | PackIt(dotnetRetireProj, dotnetRetireVersion); 45 | PackIt(dotnetMiddlewareProj, runtimeCheckersVersion); 46 | PackIt(dotnetBackgroundServiceProj, runtimeCheckersVersion); 47 | }); 48 | 49 | private void PackIt(string project, string version, string tfm = null) 50 | { 51 | var coresettings = new DotNetCorePackSettings 52 | { 53 | Configuration = "Release", 54 | OutputDirectory = outputDir, 55 | NoBuild = true 56 | }; 57 | coresettings.MSBuildSettings = new DotNetCoreMSBuildSettings() 58 | .WithProperty("Version", new[] { version }); 59 | 60 | 61 | DotNetCorePack(project, coresettings); 62 | } 63 | 64 | Task("PublishTool") 65 | .IsDependentOn("Pack") 66 | .Does(() => { 67 | var settings = new DotNetCoreNuGetPushSettings 68 | { 69 | Source = "https://api.nuget.org/v3/index.json", 70 | ApiKey = EnvironmentVariable("NUGET_API_KEY") 71 | }; 72 | 73 | DotNetCoreNuGetPush($"{outputDir}/{dotnetRetirePackageId}.{dotnetRetireVersion}.nupkg", settings); 74 | }); 75 | 76 | Task("PublishRuntimeCheckers") 77 | .IsDependentOn("Pack") 78 | .Does(() => { 79 | var settings = new DotNetCoreNuGetPushSettings 80 | { 81 | Source = "https://api.nuget.org/v3/index.json", 82 | ApiKey = EnvironmentVariable("NUGET_API_KEY") 83 | }; 84 | DotNetCoreNuGetPush($"{outputDir}/{dotnetMiddlewarePackageId}.{runtimeCheckersVersion}.nupkg", settings); 85 | DotNetCoreNuGetPush($"{outputDir}/{dotnetBackgroundServicePackageId}.{runtimeCheckersVersion}.nupkg", settings); 86 | }); 87 | 88 | RunTarget(target); 89 | -------------------------------------------------------------------------------- /dotnet-retire.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.6 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SampleProjects", "SampleProjects", "{0FA6F455-D528-4C98-9635-271B4EB51ACC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VulnerableRunTimeWebApp", "SampleProjects\VulnerableRunTimeWebApp\VulnerableRunTimeWebApp.csproj", "{B71C0875-44CD-4511-B0CC-B91E11FB2094}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{117AD616-E33A-4183-8CD5-C9BA137C0838}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{FB569CAF-B2B8-4145-8CDF-93150FCE9C8B}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RetireNet.Runtimes.Middleware", "src\RetireNet.Runtimes.Middleware\RetireNet.Runtimes.Middleware.csproj", "{3501E16B-20FF-4196-8C3D-623456C84768}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RetireNet.Runtimes.Core.Tests", "test\RetireNet.Runtimes.Core.Tests\RetireNet.Runtimes.Core.Tests.csproj", "{FC2B7245-9C8C-470E-8F0F-A581877F6BFE}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RetireNet.Runtimes.Core", "src\RetireNet.Runtimes.Core\RetireNet.Runtimes.Core.csproj", "{4F4B15D9-538E-4227-B19B-3DAD148694C1}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RetireNet.Runtimes.BackgroundServices", "src\RetireNet.Runtimes.BackgroundServices\RetireNet.Runtimes.BackgroundServices.csproj", "{1B7D756D-6800-4158-BCD1-D610BD42A346}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {B71C0875-44CD-4511-B0CC-B91E11FB2094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {B71C0875-44CD-4511-B0CC-B91E11FB2094}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {B71C0875-44CD-4511-B0CC-B91E11FB2094}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {B71C0875-44CD-4511-B0CC-B91E11FB2094}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {3501E16B-20FF-4196-8C3D-623456C84768}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {3501E16B-20FF-4196-8C3D-623456C84768}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {3501E16B-20FF-4196-8C3D-623456C84768}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {3501E16B-20FF-4196-8C3D-623456C84768}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {FC2B7245-9C8C-470E-8F0F-A581877F6BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {FC2B7245-9C8C-470E-8F0F-A581877F6BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {FC2B7245-9C8C-470E-8F0F-A581877F6BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {FC2B7245-9C8C-470E-8F0F-A581877F6BFE}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {4F4B15D9-538E-4227-B19B-3DAD148694C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {4F4B15D9-538E-4227-B19B-3DAD148694C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {4F4B15D9-538E-4227-B19B-3DAD148694C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {4F4B15D9-538E-4227-B19B-3DAD148694C1}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {1B7D756D-6800-4158-BCD1-D610BD42A346}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {1B7D756D-6800-4158-BCD1-D610BD42A346}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {1B7D756D-6800-4158-BCD1-D610BD42A346}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {1B7D756D-6800-4158-BCD1-D610BD42A346}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(NestedProjects) = preSolution 53 | {B71C0875-44CD-4511-B0CC-B91E11FB2094} = {0FA6F455-D528-4C98-9635-271B4EB51ACC} 54 | {3501E16B-20FF-4196-8C3D-623456C84768} = {117AD616-E33A-4183-8CD5-C9BA137C0838} 55 | {FC2B7245-9C8C-470E-8F0F-A581877F6BFE} = {FB569CAF-B2B8-4145-8CDF-93150FCE9C8B} 56 | {4F4B15D9-538E-4227-B19B-3DAD148694C1} = {117AD616-E33A-4183-8CD5-C9BA137C0838} 57 | {1B7D756D-6800-4158-BCD1-D610BD42A346} = {117AD616-E33A-4183-8CD5-C9BA137C0838} 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /gitprune.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # deletes branches merged to master at the remote (origin) 3 | 4 | git fetch --prune 5 | branch=$(git rev-parse --abbrev-ref HEAD) 6 | git branch -r --merged | grep origin | grep -v '>' | grep -v master | grep -v $branch | awk '{split($0,a,"origin/"); print a[2]}' | xargs git push origin --delete 7 | git branch --merged | grep -v master | grep -v $branch | xargs git branch -d 8 | git remote prune origin 9 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RetireNet/dotnet-retire/006af9a9c357ba56594aafd65b9a28ddd39070c3/images/logo.png -------------------------------------------------------------------------------- /install.cmd: -------------------------------------------------------------------------------- 1 | dotnet tool uninstall -g dotnet-retire 2 | dotnet tool install -g --add-source ./dotnet-retire/bin/debug dotnet-retire --version 1.0.0 3 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.BackgroundServices/RetireNet.Runtimes.BackgroundServices.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net5.0;net6.0 4 | preview 5 | enable 6 | RetireNet.Runtimes.BackgroundServices 7 | John Korsnes 8 | 9 | An .NET Core BackgroundService to report vulnerable runtimes via logging 10 | 11 | John Korsnes 12 | dotnet;retire;vulnerable;scanning;security 13 | https://github.com/RetireNet/dotnet-retire 14 | https://raw.githubusercontent.com/RetireNet/dotnet-retire/master/LICENSE 15 | https://github.com/RetireNet/dotnet-retire 16 | logo.png 17 | README.md 18 | git 19 | $(TargetsForTfmSpecificBuildOutput);IncludeP2PAssets 20 | 6.0.0 21 | 5.0.0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.BackgroundServices/RetireRuntimeBackgroundService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Extensions.Options; 4 | using RetireNet.Runtimes.Core; 5 | using RetireNet.Runtimes.Core.Clients.Models; 6 | 7 | namespace RetireNet.Runtimes.BackgroundServices; 8 | 9 | public class RetireRuntimeBackgroundService : BackgroundService 10 | { 11 | private readonly RetireRuntimeBackgroundServiceOptions _options; 12 | private readonly ReportGenerator _reportGenerator; 13 | private readonly ILogger _logger; 14 | 15 | public RetireRuntimeBackgroundService(IOptions options, ReportGenerator reportGenerator, ILogger logger) 16 | { 17 | _options = options.Value; 18 | _reportGenerator = reportGenerator; 19 | _logger = logger; 20 | } 21 | 22 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 23 | { 24 | var optionsCheckInterval = _options.CheckInterval; 25 | var timespan = new TimeSpan(0, 0, 0, 0, optionsCheckInterval); 26 | _logger.LogDebug($"Runtime vulnerability check is starting. Check interval: {timespan.ToString()}"); 27 | 28 | stoppingToken.Register(() => _logger.LogDebug("Vulnerability check is stopping.")); 29 | 30 | while (!stoppingToken.IsCancellationRequested) 31 | { 32 | _logger.LogDebug("Running runtime vulnerability check"); 33 | 34 | 35 | var report = await _reportGenerator.GetReport(AppRunTimeDetails.Build()); 36 | if (report.IsVulnerable.HasValue && report.IsVulnerable.Value) 37 | { 38 | _logger.LogWarning("Running on vulnerable runtime {VulnerableRuntime}. Security release {SecurityPatch}", report.AppRuntimeDetails.AppRuntimeVersion, report.SecurityRelease.RuntimeVersion); 39 | 40 | } 41 | await Task.Delay(optionsCheckInterval, stoppingToken); 42 | } 43 | 44 | _logger.LogDebug($"GracePeriod background task is stopping."); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.BackgroundServices/RetireRuntimeBackgroundServiceOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RetireNet.Runtimes.BackgroundServices; 2 | 3 | public class RetireRuntimeBackgroundServiceOptions 4 | { 5 | public int CheckInterval { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.BackgroundServices/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using RetireNet.Runtimes.BackgroundServices; 2 | using RetireNet.Runtimes.Core; 3 | 4 | // ReSharper disable once CheckNamespace 5 | // On purpose to avoid cluttering hosts with new package namespace 6 | namespace Microsoft.Extensions.DependencyInjection; 7 | 8 | public static class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection AddRetireRuntimeHostedService(this IServiceCollection services, Action configurator = null) 11 | { 12 | if (configurator == null) 13 | { 14 | services.Configure(c => 15 | { 16 | var oneHourInMillis = 60 * 60 * 1000; 17 | c.CheckInterval = oneHourInMillis; 18 | }); 19 | } 20 | else 21 | { 22 | services.Configure(configurator); 23 | } 24 | 25 | services.AddSingleton(); 26 | return services.AddHostedService(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/Clients/Models/AppRunTimeDetails.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Runtime.Versioning; 4 | 5 | namespace RetireNet.Runtimes.Core.Clients.Models; 6 | 7 | public class AppRunTimeDetails 8 | { 9 | private AppRunTimeDetails() 10 | { 11 | 12 | } 13 | public static AppRunTimeDetails Build(string runtime = null) 14 | { 15 | return new AppRunTimeDetails 16 | { 17 | Os = GetOperatingSystem(), 18 | OsPlatform = RuntimeInformation.OSDescription, 19 | OsArchitecture = RuntimeInformation.OSArchitecture.ToString(), 20 | OsBits = Environment.Is64BitOperatingSystem ? "64" : "32", 21 | AppTargetFramework = Assembly.GetEntryAssembly()?.GetCustomAttribute()?.FrameworkName, 22 | AppRuntimeVersion = runtime ?? GetNetCoreAppRuntimeVersion(), 23 | AppBits = Environment.Is64BitProcess ? "64" : "32", 24 | }; 25 | } 26 | public string Os { get; set; } 27 | 28 | public string OsPlatform { get; set; } 29 | 30 | public string OsArchitecture { get; set; } 31 | 32 | public string OsBits { get; set; } 33 | 34 | public string AppTargetFramework { get; set; } 35 | public string AppRuntimeVersion { get; set; } 36 | 37 | public string AppBits { get; set; } 38 | 39 | private static string GetOperatingSystem() 40 | { 41 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 42 | return OSPlatform.Windows.ToString(); 43 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 44 | return OSPlatform.Linux.ToString(); 45 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 46 | return OSPlatform.OSX.ToString(); 47 | return string.Empty; 48 | } 49 | 50 | public static string GetNetCoreAppRuntimeVersion() 51 | { 52 | return GetRuntimeVersion("Microsoft.NETCore.App"); 53 | } 54 | 55 | private static string GetRuntimeVersion(string framework) 56 | { 57 | var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; 58 | var assemblyPath = assembly.Location.Split(new[] {'/', '\\'}, StringSplitOptions.RemoveEmptyEntries); 59 | int netCoreAppIndex = Array.IndexOf(assemblyPath, framework); 60 | if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) 61 | return assemblyPath[netCoreAppIndex + 1]; 62 | return null; 63 | } 64 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/Clients/Models/CVE.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Text.Json.Serialization; 3 | 4 | namespace RetireNet.Runtimes.Core.Clients.Models; 5 | 6 | public class CVE 7 | { 8 | [JsonPropertyName("cve-id")] 9 | public string Id { get; set; } 10 | 11 | [JsonPropertyName("cve-url")] 12 | public string Url { get; set; } 13 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/Clients/Models/Channel.cs: -------------------------------------------------------------------------------- 1 | namespace RetireNet.Runtimes.Core.Clients.Models; 2 | 3 | public class Channel 4 | { 5 | public Channel() 6 | { 7 | Releases = new List(); 8 | } 9 | 10 | public List Releases { get; set; } 11 | 12 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/Clients/Models/Release.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace RetireNet.Runtimes.Core.Clients.Models; 4 | 5 | public class Release 6 | { 7 | public Release() 8 | { 9 | CVEs = new List(); 10 | } 11 | public string RuntimeVersion { get; set; } 12 | public IEnumerable CVEs { get; set; } 13 | 14 | [JsonIgnore] 15 | public bool Security { get; set; } 16 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/Clients/Models/Report.cs: -------------------------------------------------------------------------------- 1 | namespace RetireNet.Runtimes.Core.Clients.Models; 2 | 3 | public class Report 4 | { 5 | public bool? IsVulnerable { get; set; } 6 | public string Details { get; set; } 7 | public AppRunTimeDetails AppRuntimeDetails { get; set; } 8 | public Release SecurityRelease { get; set; } 9 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/Clients/ReleaseMetadataClient.cs: -------------------------------------------------------------------------------- 1 | using RetireNet.Runtimes.Core.Clients.Models; 2 | using RetireNet.Runtimes.Core.HttpClients; 3 | 4 | namespace RetireNet.Runtimes.Core.Clients; 5 | 6 | public class ReleaseMetadataClient 7 | { 8 | private readonly ReleaseMetadataHttpClient _httpClient; 9 | 10 | public ReleaseMetadataClient() 11 | { 12 | _httpClient = new ReleaseMetadataHttpClient(); 13 | } 14 | 15 | public async Task GetChannel(string appRuntimeVersion) 16 | { 17 | var allChannels = await _httpClient.GetAllChannelsAsync(); 18 | 19 | foreach (var singleChannel in allChannels) 20 | { 21 | var isChannelContainingRuntimeRelease = singleChannel.Releases.Any(r => r.Runtime != null && r.Runtime.Version == appRuntimeVersion); 22 | if (isChannelContainingRuntimeRelease) 23 | { 24 | var channel = new Channel(); 25 | channel.Releases.AddRange(singleChannel.Releases.Select(r => 26 | { 27 | var release = new Release 28 | { 29 | RuntimeVersion = r.Runtime?.Version 30 | }; 31 | 32 | if (r.Security) 33 | { 34 | release.Security = r.Security; 35 | release.CVEs = r.CVEs?.Select(c => new CVE 36 | { 37 | Id = c.Id, 38 | Url = c.Url 39 | }); 40 | } 41 | return release; 42 | })); 43 | return channel; 44 | } 45 | } 46 | 47 | return null; 48 | } 49 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/HttpClients/Models/Channels/Channel.cs: -------------------------------------------------------------------------------- 1 | namespace RetireNet.Runtimes.Core.HttpClients.Models.Channels; 2 | 3 | internal class Channel 4 | { 5 | public Channel() 6 | { 7 | Releases = new List(); 8 | } 9 | public List Releases { get; set; } 10 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/HttpClients/Models/Channels/Release.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using RetireNet.Runtimes.Core.Clients.Models; 3 | 4 | namespace RetireNet.Runtimes.Core.HttpClients.Models.Channels; 5 | 6 | internal class Release 7 | { 8 | public Release() 9 | { 10 | CVEs = new List(); 11 | } 12 | 13 | [JsonPropertyName("release-version")] 14 | public string ReleaseVersion { get; set; } 15 | 16 | [JsonPropertyName("cve-list")] 17 | public List CVEs { get; set; } 18 | 19 | public Runtime Runtime { get; set; } 20 | 21 | public bool Security { get; set; } 22 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/HttpClients/Models/Channels/Runtime.cs: -------------------------------------------------------------------------------- 1 | namespace RetireNet.Runtimes.Core.HttpClients.Models.Channels; 2 | 3 | public class Runtime 4 | { 5 | public string Version { get; set; } 6 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/HttpClients/Models/Index/Channel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace RetireNet.Runtimes.Core.HttpClients.Models.Index; 4 | 5 | internal class Channel 6 | { 7 | [JsonPropertyName("releases.json")] 8 | public Uri ReleasesUrl { get; set; } 9 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/HttpClients/Models/Index/ReleaseIndex.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace RetireNet.Runtimes.Core.HttpClients.Models.Index; 4 | 5 | internal class ReleaseIndex 6 | { 7 | public ReleaseIndex() 8 | { 9 | Channels = new List(); 10 | } 11 | 12 | [JsonPropertyName("releases-index")] 13 | public IEnumerable Channels { get; set; } 14 | } -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/HttpClients/ReleaseMetadataHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using System.Runtime.CompilerServices; 3 | using System.Text.Json; 4 | using RetireNet.Runtimes.Core.HttpClients.Models.Channels; 5 | 6 | [assembly: InternalsVisibleTo("RetireNet.Runtimes.Core.Tests")] 7 | namespace RetireNet.Runtimes.Core.HttpClients; 8 | 9 | internal class ReleaseMetadataHttpClient 10 | { 11 | private readonly HttpClient _httpClient; 12 | 13 | public ReleaseMetadataHttpClient() 14 | { 15 | _httpClient = new HttpClient 16 | { 17 | Timeout = TimeSpan.FromSeconds(10), 18 | BaseAddress = new Uri("https://dotnetcli.blob.core.windows.net") 19 | }; 20 | } 21 | 22 | public async Task> GetAllChannelsAsync() 23 | { 24 | var index = await GetIndexAsync(); 25 | 26 | var tasks = new List>(); 27 | 28 | foreach (var singleChannel in index.Channels) 29 | { 30 | tasks.Add(GetChannel(singleChannel.ReleasesUrl)); 31 | } 32 | 33 | return await Task.WhenAll(tasks); 34 | 35 | } 36 | 37 | public async Task GetChannel(Uri url) 38 | { 39 | return await Get(url); 40 | } 41 | 42 | private async Task GetIndexAsync() 43 | { 44 | return await Get("/dotnet/release-metadata/releases-index.json"); 45 | } 46 | 47 | private async Task Get(string url) 48 | { 49 | return await _httpClient.GetFromJsonAsync(url, new JsonSerializerOptions { PropertyNameCaseInsensitive = true}); 50 | } 51 | 52 | private async Task Get(Uri url) 53 | { 54 | return await _httpClient.GetFromJsonAsync(url, new JsonSerializerOptions { PropertyNameCaseInsensitive = true}); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/ReportGenerator.cs: -------------------------------------------------------------------------------- 1 | using RetireNet.Runtimes.Core.Clients; 2 | using RetireNet.Runtimes.Core.Clients.Models; 3 | 4 | namespace RetireNet.Runtimes.Core; 5 | 6 | public class ReportGenerator 7 | { 8 | private readonly ReleaseMetadataClient _client; 9 | 10 | public ReportGenerator() : this(new ReleaseMetadataClient()) 11 | { 12 | } 13 | 14 | public ReportGenerator(ReleaseMetadataClient client) 15 | { 16 | _client = client; 17 | } 18 | 19 | public async Task GetReport(AppRunTimeDetails appRunTimeDetails) 20 | { 21 | var channel = await _client.GetChannel(appRunTimeDetails.AppRuntimeVersion); 22 | 23 | if (channel == null) 24 | { 25 | return new Report 26 | { 27 | AppRuntimeDetails = appRunTimeDetails, 28 | Details = $"Running on unknown runtime {appRunTimeDetails.AppRuntimeVersion}. Not able to check for security patches." 29 | }; 30 | } 31 | 32 | var securityRelease = channel.Releases.FirstOrDefault(r => r.Security); 33 | 34 | if (securityRelease == null) 35 | { 36 | return new Report 37 | { 38 | AppRuntimeDetails = appRunTimeDetails, 39 | IsVulnerable = false 40 | }; 41 | } 42 | 43 | var secReleaseVersion = Version.Parse(securityRelease.RuntimeVersion); 44 | var appReleaseVersion = Version.Parse(appRunTimeDetails.AppRuntimeVersion); 45 | 46 | if (appReleaseVersion >= secReleaseVersion) 47 | { 48 | return new Report 49 | { 50 | AppRuntimeDetails = appRunTimeDetails, 51 | IsVulnerable = false 52 | }; 53 | } 54 | 55 | return new Report 56 | { 57 | IsVulnerable = true, 58 | AppRuntimeDetails = appRunTimeDetails, 59 | SecurityRelease = securityRelease 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Core/RetireNet.Runtimes.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net5.0;net6.0 4 | preview 5 | enable 6 | false 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Middleware/AppBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using RetireNet.Runtimes.Middleware; 2 | 3 | // ReSharper disable once CheckNamespace 4 | // On purpose to avoid cluttering hosts with new package namespace 5 | namespace Microsoft.AspNetCore.Builder 6 | { 7 | public static class AppBuilderExtensions 8 | { 9 | public static IApplicationBuilder UseRuntimeVulnerabilityReport(this IApplicationBuilder builder) 10 | { 11 | return builder.UseMiddleware(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Middleware/RetireNet.Runtimes.Middleware.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net5.0;net6.0 4 | RetireNet.Runtimes.Middleware 5 | preview 6 | enable 7 | John Korsnes 8 | 9 | An ASP.NET Core endpoint to report vulnerable runtimes 10 | 11 | John Korsnes 12 | dotnet;retire;vulnerable;scanning;security 13 | https://github.com/RetireNet/dotnet-retire 14 | https://raw.githubusercontent.com/RetireNet/dotnet-retire/master/LICENSE 15 | https://github.com/RetireNet/dotnet-retire 16 | logo.png 17 | README.md 18 | git 19 | $(TargetsForTfmSpecificBuildOutput);IncludeP2PAssets 20 | 3.1.0 21 | 5.0.0 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/RetireNet.Runtimes.Middleware/RetireRunTimeMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | using Microsoft.AspNetCore.Http; 4 | using RetireNet.Runtimes.Core; 5 | using RetireNet.Runtimes.Core.Clients.Models; 6 | 7 | namespace RetireNet.Runtimes.Middleware; 8 | 9 | internal class RetireRunTimeMiddleware 10 | { 11 | private readonly RequestDelegate _next; 12 | private readonly ReportGenerator _client; 13 | 14 | public RetireRunTimeMiddleware(RequestDelegate next) 15 | { 16 | _next = next; 17 | _client = new ReportGenerator(); 18 | } 19 | 20 | public async Task InvokeAsync(HttpContext context) 21 | { 22 | var report = await _client.GetReport(AppRunTimeDetails.Build()); 23 | 24 | var json = JsonSerializer.Serialize(report, new JsonSerializerOptions 25 | { 26 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 27 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, 28 | }); 29 | 30 | context.Response.OnStarting(state => 31 | { 32 | context.Response.ContentType = "application/json"; 33 | return Task.CompletedTask; 34 | }, null); 35 | 36 | await context.Response.WriteAsync(json); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/RetireNet.Runtimes.Core.Tests/RetireNet.Runtimes.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0;net6.0 5 | RetireRuntimeMiddleware.Tests 6 | preview 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/RetireNet.Runtimes.Core.Tests/RuntimeReportIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using RetireNet.Runtimes.Core; 2 | using RetireNet.Runtimes.Core.Clients; 3 | using RetireNet.Runtimes.Core.Clients.Models; 4 | using RetireNet.Runtimes.Core.HttpClients; 5 | using Xunit; 6 | 7 | namespace RetireRuntimeMiddleware.Tests; 8 | 9 | public class RuntimeReportIntegrationTests 10 | { 11 | [Theory()] 12 | [InlineData("3.1.0", true)] 13 | [InlineData("3.1.1", true)] 14 | [InlineData("3.1.5", true)] 15 | [InlineData("3.1.20", false)] 16 | [InlineData("5.0.10", true)] 17 | [InlineData("5.0.11", false)] 18 | public async Task VulnerabilityReports(string version, bool isVulnerable) 19 | { 20 | var client = new ReportGenerator(); 21 | var report = await client.GetReport(AppRunTimeDetails.Build(version)); 22 | Assert.Equal(isVulnerable, report.IsVulnerable); 23 | } 24 | 25 | [Fact] 26 | public async Task UnknownRuntimes() 27 | { 28 | var client = new ReportGenerator(); 29 | var report = await client.GetReport(AppRunTimeDetails.Build("abc")); 30 | Assert.Null(report.IsVulnerable); 31 | Assert.Equal("Running on unknown runtime abc. Not able to check for security patches.", report.Details); 32 | } 33 | 34 | [Fact(Skip = "Testing all releases. Slow.")] 35 | public async Task CanGetReportForAllRuntimes() 36 | { 37 | var httpClient = new ReleaseMetadataHttpClient(); 38 | var allChannels = await httpClient.GetAllChannelsAsync(); 39 | foreach (var channel in allChannels) 40 | { 41 | foreach (var release in channel.Releases) 42 | { 43 | var releaseMetadataClient = new ReleaseMetadataClient(); 44 | var client = new ReportGenerator(releaseMetadataClient); 45 | if (release.Runtime != null) 46 | { 47 | var report = await client.GetReport(AppRunTimeDetails.Build(release.Runtime.Version)); 48 | Assert.NotNull(report); 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tools/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | --------------------------------------------------------------------------------