Skip to content

Commit

Permalink
feat(scan): implement CI discovery provider
Browse files Browse the repository at this point in the history
  • Loading branch information
ostridm committed Dec 9, 2022
1 parent 48054c5 commit 8299192
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 196 deletions.
168 changes: 121 additions & 47 deletions src/SecTester.Scan/CI/CiServer.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,125 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace SecTester.Scan.CI;

public record CiServer(string? ServerName = default)
[DebuggerDisplay("{Constant},{Name}")]
public sealed class CiServer : IEquatable<CiServer>
{
public static CiServer APPCENTER { get; } = new("Visual Studio App Center");
public static CiServer APPCIRCLE { get; } = new("Appcircle");
public static CiServer APPVEYOR { get; } = new("AppVeyor");
public static CiServer AZURE_PIPELINES { get; } = new("Azure Pipelines");
public static CiServer BAMBOO { get; } = new("Bamboo");
public static CiServer BITBUCKET { get; } = new("Bitbucket Pipelines");
public static CiServer BITRISE { get; } = new("Bitrise");
public static CiServer BUDDY { get; } = new("Buddy");
public static CiServer BUILDKITE { get; } = new("Buildkite");
public static CiServer CIRCLE { get; } = new("CircleCI");
public static CiServer CIRRUS { get; } = new("Cirrus CI");
public static CiServer CODEBUILD { get; } = new("AWS CodeBuild");
public static CiServer CODEFRESH { get; } = new("Codefresh");
public static CiServer CODEMAGIC { get; } = new("Codemagic");
public static CiServer CODESHIP { get; } = new("Codeship");
public static CiServer DRONE { get; } = new("Drone");
public static CiServer DSARI { get; } = new("dsari");
public static CiServer EAS { get; } = new("Expo Application Services");
public static CiServer GERRIT { get; } = new("Gerrit");
public static CiServer GITHUB_ACTIONS { get; } = new("GitHub Actions");
public static CiServer GITLAB { get; } = new("GitLab CI");
public static CiServer GOCD { get; } = new("GoCD");
public static CiServer GOOGLE_CLOUD_BUILD { get; } = new("Google Cloud Build");
public static CiServer HEROKU { get; } = new("Heroku");
public static CiServer HUDSON { get; } = new("Hudson");
public static CiServer JENKINS { get; } = new("Jenkins");
public static CiServer LAYERCI { get; } = new("LayerCI");
public static CiServer MAGNUM { get; } = new("Magnum CI");
public static CiServer NETLIFY { get; } = new("Netlify CI");
public static CiServer NEVERCODE { get; } = new("Nevercode");
public static CiServer RELEASEHUB { get; } = new("ReleaseHub");
public static CiServer RENDER { get; } = new("Render");
public static CiServer SAIL { get; } = new("Sail CI");
public static CiServer SCREWDRIVER { get; } = new("Screwdriver");
public static CiServer SEMAPHORE { get; } = new("Semaphore");
public static CiServer SHIPPABLE { get; } = new("Shippable");
public static CiServer SOLANO { get; } = new("Solano CI");
public static CiServer SOURCEHUT { get; } = new("Sourcehut");
public static CiServer STRIDER { get; } = new("Strider CD");
public static CiServer TASKCLUSTER { get; } = new("TaskCluster");
public static CiServer TEAMCITY { get; } = new("TeamCity");
public static CiServer TRAVIS { get; } = new("Travis CI");
public static CiServer VERCEL { get; } = new("Vercel");
public static CiServer WOODPECKER { get; } = new("Woodpecker");
public static CiServer XCODE_CLOUD { get; } = new("Xcode Cloud");
public static CiServer XCODE_SERVER { get; } = new("Xcode Server");
public static CiServer AppCenter { get; } = new("APPCENTER", "Visual Studio App Center");
public static CiServer AppCircle { get; } = new("APPCIRCLE", "Appcircle");
public static CiServer Appveyor { get; } = new("APPVEYOR", "AppVeyor");
public static CiServer AzurePipelines { get; } = new("AZURE_PIPELINES", "Azure Pipelines");
public static CiServer Bamboo { get; } = new("BAMBOO", "Bamboo");
public static CiServer Bitbucket { get; } = new("BITBUCKET", "Bitbucket Pipelines");
public static CiServer Bitrise { get; } = new("BITRISE", "Bitrise");
public static CiServer Buddy { get; } = new("BUDDY", "Buddy");
public static CiServer BuildKite { get; } = new("BUILDKITE", "Buildkite");
public static CiServer Circle { get; } = new("CIRCLE", "CircleCI");
public static CiServer Cirrus { get; } = new("CIRRUS", "Cirrus CI");
public static CiServer CodeBuild { get; } = new("CODEBUILD", "AWS CodeBuild");
public static CiServer CodeFresh { get; } = new("CODEFRESH", "Codefresh");
public static CiServer CodeMagic { get; } = new("CODEMAGIC", "Codemagic");
public static CiServer CodeShip { get; } = new("CODESHIP", "Codeship");
public static CiServer Drone { get; } = new("DRONE", "Drone");
public static CiServer Dsari { get; } = new("DSARI", "dsari");
public static CiServer Eas { get; } = new("EAS", "Expo Application Services");
public static CiServer Gerrit { get; } = new("GERRIT", "Gerrit");
public static CiServer GithubActions { get; } = new("GITHUB_ACTIONS", "GitHub Actions");
public static CiServer Gitlab { get; } = new("GITLAB", "GitLab CI");
public static CiServer GoCd { get; } = new("GOCD", "GoCD");
public static CiServer GoogleCloudBuild { get; } = new("GOOGLE_CLOUD_BUILD", "Google Cloud Build");
public static CiServer Heroku { get; } = new("HEROKU", "Heroku");
public static CiServer Hudson { get; } = new("HUDSON", "Hudson");
public static CiServer Jenkins { get; } = new("JENKINS", "Jenkins");
public static CiServer LayerCi { get; } = new("LAYERCI", "LayerCI");
public static CiServer Magnum { get; } = new("MAGNUM", "Magnum CI");
public static CiServer Netlify { get; } = new("NETLIFY", "Netlify CI");
public static CiServer NeverCode { get; } = new("NEVERCODE", "Nevercode");
public static CiServer ReleaseHub { get; } = new("RELEASEHUB", "ReleaseHub");
public static CiServer Render { get; } = new("RENDER", "Render");
public static CiServer Sail { get; } = new("SAIL", "Sail CI");
public static CiServer Screwdriver { get; } = new("SCREWDRIVER", "Screwdriver");
public static CiServer Semaphore { get; } = new("SEMAPHORE", "Semaphore");
public static CiServer Shippable { get; } = new("SHIPPABLE", "Shippable");
public static CiServer Solano { get; } = new("SOLANO", "Solano CI");
public static CiServer SourceHut { get; } = new("SOURCEHUT", "Sourcehut");
public static CiServer Strider { get; } = new("STRIDER", "Strider CD");
public static CiServer TaskCluster { get; } = new("TASKCLUSTER", "TaskCluster");
public static CiServer Teamcity { get; } = new("TEAMCITY", "TeamCity");
public static CiServer Travis { get; } = new("TRAVIS", "Travis CI");
public static CiServer Vercel { get; } = new("VERCEL", "Vercel");
public static CiServer Woodpecker { get; } = new("WOODPECKER", "Woodpecker");
public static CiServer XcodeCloud { get; } = new("XCODE_CLOUD", "Xcode Cloud");
public static CiServer XcodeServer { get; } = new("XCODE_SERVER", "Xcode Server");

private readonly string _constant;
private readonly string _name;
private readonly int _hashcode;

public string Constant => _constant;
public string Name => _name;

private CiServer(string constant, string name)
{
_constant = constant;
_name = name;
_hashcode = StringComparer.OrdinalIgnoreCase.GetHashCode(_constant);
}

public override int GetHashCode()
{
return _hashcode;
}

public override string ToString()
{
return _constant;
}

public override bool Equals(object? obj)
{
return Equals(obj as CiServer);
}

public bool Equals(CiServer? other)
{
if (other is null)
{
return false;
}

return ReferenceEquals(Constant, other.Constant) || Constant.Equals(other.Constant, StringComparison.OrdinalIgnoreCase);
}

public static bool operator ==(CiServer? left, CiServer? right)
{
return left is null || right is null ? ReferenceEquals(left, right) : left.Equals(right);
}

public static bool operator !=(CiServer? left, CiServer? right)
{
return !(left == right);
}

internal static CiServer From(Vendor vendor)
{
return From(vendor.Constant, vendor.Name);
}
public static CiServer From(string constant, string name)
{
if (string.IsNullOrEmpty(constant))
{
throw new ArgumentException("Constant value must not be empty.");
}

return typeof(CiServer)
.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Select(x => x.GetValue(null))
.Cast<CiServer>()
.FirstOrDefault(x => x.Constant.Equals(constant, StringComparison.OrdinalIgnoreCase))
?? new CiServer(constant, name);
}
}
15 changes: 5 additions & 10 deletions src/SecTester.Scan/CI/DefaultCiDiscovery.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text.Json;

namespace SecTester.Scan.CI;

internal class DefaultCiDiscovery : CiDiscovery
{
public const string VendorsResource = "SecTester.Scan.CI.vendors.json";
public CiServer? Server { get; }

public bool IsCi => Server != null;
Expand All @@ -19,7 +19,7 @@ public DefaultCiDiscovery(IDictionary? env = default)
env ??= Environment.GetEnvironmentVariables();

var vendors = JsonSerializer.Deserialize<Vendor[]>(
ResourceUtils.GetEmbeddedResourceContent<DefaultCiDiscovery>("SecTester.Scan.CI.vendors.json"),
ResourceUtils.GetEmbeddedResourceContent<DefaultCiDiscovery>(VendorsResource),
new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });

if (vendors is null)
Expand All @@ -29,20 +29,15 @@ public DefaultCiDiscovery(IDictionary? env = default)

var matcher = new VendorMatcher(env);

var vendor = vendors.FirstOrDefault(x => matcher.MatchEnvElement(x.Env));
var vendor = vendors.FirstOrDefault(x => matcher.MatchEnv(x.Env));

if (vendor is null)
{
return;
}

Server = typeof(CiServer)
.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(x => x.Name.Equals(vendor.Constant, StringComparison.OrdinalIgnoreCase))
.Select(x => x.GetValue(null))
.Cast<CiServer>()
.FirstOrDefault() ?? new CiServer(vendor.Name);
Server = CiServer.From(vendor);

IsPr = matcher.MatchPrElement(vendor.Pr);
IsPr = matcher.MatchPr(vendor.Pr);
}
}
47 changes: 24 additions & 23 deletions src/SecTester.Scan/CI/VendorMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,51 +13,52 @@ public VendorMatcher(IDictionary env)
_env = env;
}

public bool MatchEnvElement(JsonElement element)
public bool MatchEnv(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString() is not null && _env.Contains(element.GetString()!),
JsonValueKind.Object => MatchInnerAny(element) || MatchInnerEnvIncludes(element) ||
MatchOwnProperties(element),
JsonValueKind.Array => element.EnumerateArray()
.All(x => x.ValueKind == JsonValueKind.String && x.GetString() is not null && _env.Contains(x.GetString()!)),
JsonValueKind.String => CheckStringValue(element),
JsonValueKind.Object => Any(element) || Includes(element) || HasOwnProperties(element),
JsonValueKind.Array => element.EnumerateArray().All(CheckStringValue),
_ => false
};
}


public bool MatchPrElement(JsonElement element)
public bool MatchPr(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString() is not null && _env.Contains(element.GetString()!),
JsonValueKind.Object => MatchInnerEnvNe(element) || MatchEnvElement(element),
JsonValueKind.String => CheckStringValue(element),
JsonValueKind.Object => NotEqual(element) || MatchEnv(element),
_ => false
};
}

private bool MatchOwnProperties(JsonElement element)
private bool CheckStringValue(JsonElement element)
{
return element.ValueKind == JsonValueKind.String && !string.IsNullOrEmpty(element.GetString()) && _env.Contains(element.GetString()!);
}

private bool HasOwnProperties(JsonElement element)
{
return element.EnumerateObject().All(x =>
x.Value.ValueKind == JsonValueKind.String && _env.Contains(x.Name) &&
x.Value.ValueEquals(_env[x.Name]?.ToString()));
}

private bool MatchInnerAny(JsonElement element)
private bool Any(JsonElement element)
{
var any = element.EnumerateObject().FirstOrDefault(x => x.NameEquals("any"));

return any.Value.ValueKind == JsonValueKind.Array && any.Value.EnumerateArray()
.Any(x => x.ValueKind == JsonValueKind.String && x.GetString() is not null && _env.Contains(x.GetString()!));
return any.Value.ValueKind == JsonValueKind.Array && any.Value.EnumerateArray().Any(CheckStringValue);
}

private bool MatchInnerEnvIncludes(JsonElement element)
private bool Includes(JsonElement element)
{
var env = GetInnerPropertyValue(element, "env");
var includes = GetInnerPropertyValue(element, "includes");
var env = GetPropertyValue(element, "env");
var includes = GetPropertyValue(element, "includes");

if (env is null || includes is null || !_env.Contains(env))
if (string.IsNullOrEmpty(env) || string.IsNullOrEmpty(includes) || !_env.Contains(env))
{
return false;
}
Expand All @@ -66,12 +67,12 @@ private bool MatchInnerEnvIncludes(JsonElement element)
return envVarValue?.Contains(includes) ?? false;
}

private bool MatchInnerEnvNe(JsonElement element)
private bool NotEqual(JsonElement element)
{
var env = GetInnerPropertyValue(element, "env");
var ne = GetInnerPropertyValue(element, "ne");
var env = GetPropertyValue(element, "env");
var ne = GetPropertyValue(element, "ne");

if (env is null || ne is null || !_env.Contains(env) || ne != "false")
if (string.IsNullOrEmpty(env) || string.IsNullOrEmpty(ne) || !_env.Contains(env) || ne != "false")
{
return false;
}
Expand All @@ -80,7 +81,7 @@ private bool MatchInnerEnvNe(JsonElement element)
return !string.IsNullOrEmpty(envVarValue);
}

private static string? GetInnerPropertyValue(JsonElement element, string name)
private static string? GetPropertyValue(JsonElement element, string name)
{
var property = element.EnumerateObject().FirstOrDefault(x => x.NameEquals(name));
return property.Value.ValueKind == JsonValueKind.String ? property.Value.GetString() : default;
Expand Down
2 changes: 1 addition & 1 deletion src/SecTester.Scan/DefaultScans.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public DefaultScans(Configuration configuration, CommandDispatcher commandDispat

public async Task<string> CreateScan(ScanConfig config)
{
var command = new CreateScan(config, _configuration.Name, _configuration.Version, _ciDiscovery.Server?.ServerName);
var command = new CreateScan(config, _configuration.Name, _configuration.Version, _ciDiscovery.Server?.Name);

var result = await SendCommand(command).ConfigureAwait(false);

Expand Down
Loading

0 comments on commit 8299192

Please sign in to comment.