-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding CratesApiKey validator (#531)
* Adding CratesApiKey validator * Addressing PR feedback
- Loading branch information
Showing
7 changed files
with
290 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
Src/Plugins/Security/SEC101_047.CratesApiKeyValidator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Net.Http; | ||
|
||
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk; | ||
using Microsoft.RE2.Managed; | ||
|
||
namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security | ||
{ | ||
public class CratesApiKeyValidator : ValidatorBase | ||
{ | ||
internal static CratesApiKeyValidator Instance; | ||
|
||
static CratesApiKeyValidator() | ||
{ | ||
Instance = new CratesApiKeyValidator(); | ||
} | ||
|
||
public static IEnumerable<ValidationResult> IsValidStatic(Dictionary<string, FlexMatch> groups) | ||
{ | ||
return IsValidStatic(Instance, groups); | ||
} | ||
|
||
public static ValidationState IsValidDynamic(ref Fingerprint fingerprint, | ||
ref string message, | ||
Dictionary<string, string> options, | ||
ref ResultLevelKind resultLevelKind) | ||
{ | ||
return IsValidDynamic(Instance, | ||
ref fingerprint, | ||
ref message, | ||
options, | ||
ref resultLevelKind); | ||
} | ||
|
||
protected override IEnumerable<ValidationResult> IsValidStaticHelper(Dictionary<string, FlexMatch> groups) | ||
{ | ||
FlexMatch secret = groups["secret"]; | ||
|
||
if (!ContainsDigitAndChar(secret.Value)) | ||
{ | ||
return ValidationResult.CreateNoMatch(); | ||
} | ||
|
||
var validationResult = new ValidationResult | ||
{ | ||
Fingerprint = new Fingerprint | ||
{ | ||
Secret = secret.Value, | ||
Platform = nameof(AssetPlatform.Crates), | ||
}, | ||
ValidationState = ValidationState.Unknown, | ||
}; | ||
|
||
return new[] { validationResult }; | ||
} | ||
|
||
protected override ValidationState IsValidDynamicHelper(ref Fingerprint fingerprint, | ||
ref string message, | ||
Dictionary<string, string> options, | ||
ref ResultLevelKind resultLevelKind) | ||
{ | ||
string secret = fingerprint.Secret; | ||
|
||
const string uri = "https://crates.io/api/v1/crates/sarif-pattern-matcher/owners"; | ||
|
||
try | ||
{ | ||
HttpClient client = CreateOrRetrieveCachedHttpClient(); | ||
|
||
using var request = new HttpRequestMessage(HttpMethod.Delete, uri); | ||
request.Headers.Add("Authorization", secret); | ||
|
||
using HttpResponseMessage response = client | ||
.SendAsync(request, HttpCompletionOption.ResponseHeadersRead) | ||
.GetAwaiter() | ||
.GetResult(); | ||
|
||
switch (response.StatusCode) | ||
{ | ||
case HttpStatusCode.OK: | ||
{ | ||
return ValidationState.Authorized; | ||
} | ||
|
||
case HttpStatusCode.Forbidden: | ||
{ | ||
return ValidationState.Unauthorized; | ||
} | ||
|
||
default: | ||
{ | ||
return ReturnUnexpectedResponseCode(ref message, response.StatusCode); | ||
} | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
return ReturnUnhandledException(ref message, e); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
...ts.Security/TestData/SecurePlaintextSecrets/ExpectedOutputs/SEC101_047.CratesApiKey.sarif
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
{ | ||
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json", | ||
"version": "2.1.0", | ||
"runs": [ | ||
{ | ||
"tool": { | ||
"driver": { | ||
"name": "testhost", | ||
"organization": "Microsoft Corporation", | ||
"product": "Microsoft.TestHost", | ||
"fullName": "testhost 15.0.0.0", | ||
"version": "15.0.0.0", | ||
"semanticVersion": "15.0.0", | ||
"rules": [ | ||
{ | ||
"id": "SEC101/047", | ||
"name": "DoNotExposePlaintextSecrets/CratesApiKey", | ||
"fullDescription": { | ||
"text": "Do not expose plaintext (or base64-encoded plaintext) secrets in versioned engineering content." | ||
}, | ||
"messageStrings": { | ||
"NotApplicable_InvalidMetadata": { | ||
"text": "'{0}' was not evaluated for check '{1}' because the analysis is not relevant for the following reason: {2}." | ||
}, | ||
"Default": { | ||
"text": "'{0}' is {1}{2}{3}{4}{5}." | ||
} | ||
}, | ||
"helpUri": "https://github.com/microsoft/sarif-pattern-matcher" | ||
} | ||
] | ||
} | ||
}, | ||
"invocations": [ | ||
{ | ||
"executionSuccessful": true | ||
} | ||
], | ||
"results": [ | ||
{ | ||
"ruleId": "SEC101/047", | ||
"ruleIndex": 0, | ||
"level": "note", | ||
"message": { | ||
"id": "Default", | ||
"arguments": [ | ||
"ciodea…", | ||
"an apparent ", | ||
"", | ||
"Crates API key", | ||
"", | ||
" (no validation occurred as it was not enabled. Pass '--dynamic-validation' on the command-line to validate this match)" | ||
] | ||
}, | ||
"locations": [ | ||
{ | ||
"physicalLocation": { | ||
"artifactLocation": { | ||
"uri": "src/Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_047.CratesApiKey.ps1", | ||
"uriBaseId": "SRC_ROOT" | ||
}, | ||
"region": { | ||
"startLine": 1, | ||
"startColumn": 1, | ||
"endLine": 1, | ||
"endColumn": 36, | ||
"charOffset": 0, | ||
"charLength": 35, | ||
"snippet": { | ||
"text": "ciodeaddeaddead123412341234dead1234" | ||
} | ||
} | ||
} | ||
} | ||
], | ||
"fingerprints": { | ||
"AssetFingerprint/v1": "[platform=Crates]", | ||
"ValidationFingerprint/v1": "[secret=ciodeaddeaddead123412341234dead1234]", | ||
"ValidationFingerprintHash/v1": "9ae3596b73b37748eac0f276b2ce08fa96a6ceebcd3270e5d4ec573df0e27e28", | ||
"AssetFingerprint/v2": "{\"platform\":\"Crates\"}", | ||
"ValidationFingerprint/v2": "{\"secret\":\"ciodeaddeaddead123412341234dead1234\"}" | ||
}, | ||
"rank": 43.89 | ||
} | ||
], | ||
"columnKind": "utf16CodeUnits" | ||
} | ||
] | ||
} |
1 change: 1 addition & 0 deletions
1
...Plugins/Tests.Security/TestData/SecurePlaintextSecrets/Inputs/SEC101_047.CratesApiKey.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ciodeaddeaddead123412341234dead1234 |
81 changes: 81 additions & 0 deletions
81
Src/Plugins/Tests.Security/Validators/SEC101_047.CratesApiKeyValidatorTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Generic; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Text; | ||
|
||
using FluentAssertions; | ||
|
||
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Helpers; | ||
using Microsoft.CodeAnalysis.Sarif.PatternMatcher.Sdk; | ||
|
||
using Xunit; | ||
|
||
namespace Microsoft.CodeAnalysis.Sarif.PatternMatcher.Plugins.Security.Validators | ||
{ | ||
public class CratesApiKeyValidatorTests | ||
{ | ||
[Fact] | ||
public void DiscordCredentialsValidator_MockHttpTests() | ||
{ | ||
string unknownMessage = null; | ||
const string fingerprintText = "[secret=b]"; | ||
ValidatorBase.ReturnUnexpectedResponseCode(ref unknownMessage, HttpStatusCode.NotFound); | ||
|
||
var testCases = new HttpMockTestCase[] | ||
{ | ||
new HttpMockTestCase | ||
{ | ||
Title = "Testing Valid Credentials", | ||
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.OK }, | ||
ExpectedValidationState = ValidationState.Authorized, | ||
}, | ||
new HttpMockTestCase | ||
{ | ||
Title = "Testing Invalid Credentials", | ||
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.Forbidden }, | ||
ExpectedValidationState = ValidationState.Unauthorized, | ||
}, | ||
new HttpMockTestCase | ||
{ | ||
Title = "Testing NotFound StatusCode", | ||
HttpStatusCodes = new List<HttpStatusCode>{ HttpStatusCode.NotFound }, | ||
ExpectedValidationState = ValidationState.Unknown, | ||
ExpectedMessage = unknownMessage | ||
}, | ||
}; | ||
|
||
var sb = new StringBuilder(); | ||
foreach (HttpMockTestCase testCase in testCases) | ||
{ | ||
string message = null; | ||
ResultLevelKind resultLevelKind = default; | ||
var fingerprint = new Fingerprint(fingerprintText); | ||
var keyValuePairs = new Dictionary<string, string>(); | ||
|
||
ValidatorHelper.ResetStaticInstance<CratesApiKeyValidator>(); | ||
|
||
using var httpClient = new HttpClient(HttpMockHelper.Mock(testCase.HttpStatusCodes[0], null)); | ||
CratesApiKeyValidator.Instance.SetHttpClient(httpClient); | ||
|
||
ValidationState currentState = CratesApiKeyValidator.IsValidDynamic(ref fingerprint, | ||
ref message, | ||
keyValuePairs, | ||
ref resultLevelKind); | ||
if (currentState != testCase.ExpectedValidationState) | ||
{ | ||
sb.AppendLine($"The test case '{testCase.Title}' was expecting '{testCase.ExpectedValidationState}' but found '{currentState}'."); | ||
} | ||
|
||
if (message != testCase.ExpectedMessage) | ||
{ | ||
sb.AppendLine($"The test case '{testCase.Title}' was expecting '{testCase.ExpectedMessage}' but found '{message}'."); | ||
} | ||
} | ||
|
||
sb.Length.Should().Be(0, sb.ToString()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters