Skip to content

Commit

Permalink
feat(scan): implement CI discovery provider
Browse files Browse the repository at this point in the history
closes #88
  • Loading branch information
ostridm committed Dec 8, 2022
1 parent af9cc6c commit 4fd4261
Show file tree
Hide file tree
Showing 14 changed files with 534 additions and 97 deletions.
2 changes: 2 additions & 0 deletions src/SecTester.Scan/CI/CiDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ public interface CiDiscovery
{
CiServer? Server { get; }
bool IsCi { get; }

bool IsPr { get; }
}
93 changes: 46 additions & 47 deletions src/SecTester.Scan/CI/CiServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,50 @@ namespace SecTester.Scan.CI;

public record CiServer(string? ServerName = default)
{
public static readonly CiServer UNKNOWN = new();
public static readonly CiServer APPCENTER = new("APPCENTER");
public static readonly CiServer APPCIRCLE = new("APPCIRCLE");
public static readonly CiServer APPVEYOR = new("APPVEYOR");
public static readonly CiServer AZURE_PIPELINES = new("AZURE_PIPELINES");
public static readonly CiServer BAMBOO = new("BAMBOO");
public static readonly CiServer BITBUCKET = new("BITBUCKET");
public static readonly CiServer BITRISE = new("BITRISE");
public static readonly CiServer BUDDY = new("BUDDY");
public static readonly CiServer BUILDKITE = new("BUILDKITE");
public static readonly CiServer CIRCLE = new("CIRCLE");
public static readonly CiServer CIRRUS = new("CIRRUS");
public static readonly CiServer CODEBUILD = new("CODEBUILD");
public static readonly CiServer CODEFRESH = new("CODEFRESH");
public static readonly CiServer CODEMAGIC = new("CODEMAGIC");
public static readonly CiServer CODESHIP = new("CODESHIP");
public static readonly CiServer DRONE = new("DRONE");
public static readonly CiServer DSARI = new("DSARI");
public static readonly CiServer EAS = new("EAS");
public static readonly CiServer GERRIT = new("GERRIT");
public static readonly CiServer GITHUB_ACTIONS = new("GITHUB_ACTIONS");
public static readonly CiServer GITLAB = new("GITLAB");
public static readonly CiServer GOCD = new("GOCD");
public static readonly CiServer GOOGLE_CLOUD_BUILD = new("GOOGLE_CLOUD_BUILD");
public static readonly CiServer HEROKU = new("HEROKU");
public static readonly CiServer HUDSON = new("HUDSON");
public static readonly CiServer JENKINS = new("JENKINS");
public static readonly CiServer LAYERCI = new("LAYERCI");
public static readonly CiServer MAGNUM = new("MAGNUM");
public static readonly CiServer NETLIFY = new("NETLIFY");
public static readonly CiServer NEVERCODE = new("NEVERCODE");
public static readonly CiServer RELEASEHUB = new("RELEASEHUB");
public static readonly CiServer RENDER = new("RENDER");
public static readonly CiServer SAIL = new("SAIL");
public static readonly CiServer SCREWDRIVER = new("SCREWDRIVER");
public static readonly CiServer SEMAPHORE = new("SEMAPHORE");
public static readonly CiServer SHIPPABLE = new("SHIPPABLE");
public static readonly CiServer SOLANO = new("SOLANO");
public static readonly CiServer SOURCEHUT = new("SOURCEHUT");
public static readonly CiServer STRIDER = new("STRIDER");
public static readonly CiServer TASKCLUSTER = new("TASKCLUSTER");
public static readonly CiServer TEAMCITY = new("TEAMCITY");
public static readonly CiServer TRAVIS = new("TRAVIS");
public static readonly CiServer VERCEL = new("VERCEL");
public static readonly CiServer WOODPECKER = new("WOODPECKER");
public static readonly CiServer XCODE_CLOUD = new("XCODE_CLOUD");
public static readonly CiServer XCODE_SERVER = new("XCODE_SERVER");
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");
}
41 changes: 34 additions & 7 deletions src/SecTester.Scan/CI/DefaultCiDiscovery.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
using System;
using System.IO;
using System.Collections;
using System.Linq;
using System.Reflection;
using SecTester.Scan.CI.Lib;
using System.Text.Json;

namespace SecTester.Scan.CI;

// TODO ([email protected]): rework using 'ci-info' compatible implementation
internal class DefaultCiDiscovery : CiDiscovery
{
public CiServer? Server { get; }

public bool IsCi => Server != null;

public bool IsPr { get; }

public DefaultCiDiscovery()

public DefaultCiDiscovery(IDictionary? env = default)
{
var vendorsContent =
ResourceUtils.GetEmbeddedFileContent<DefaultCiDiscovery>(@"SecTester.Scan.CI.Lib.vendors.json");
}
env ??= Environment.GetEnvironmentVariables();

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

if (vendors is null)
{
return;
}

var matcher = new VendorMatcher(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);

IsPr = matcher.MatchPr(vendor.Pr);
}
}
36 changes: 0 additions & 36 deletions src/SecTester.Scan/CI/Lib/ResourceUtils.cs

This file was deleted.

29 changes: 29 additions & 0 deletions src/SecTester.Scan/CI/ResourceUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace SecTester.Scan.CI;

internal static class ResourceUtils
{
public static string GetEmbeddedResourceContent<T>(string resourceName)
where T : class
{
if (string.IsNullOrWhiteSpace(resourceName))
{
throw new ArgumentNullException(nameof(resourceName));
}

var assembly = typeof(T).GetTypeInfo().Assembly;

using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream is null)
{
throw new InvalidOperationException($"Could not get stream for {resourceName} resource.");
}

using StreamReader reader = new StreamReader(stream);
return reader.ReadToEnd();
}
}
9 changes: 9 additions & 0 deletions src/SecTester.Scan/CI/Vendor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json;

namespace SecTester.Scan.CI;

internal record Vendor(string Name, string Constant)
{
public JsonElement Env { get; init; }
public JsonElement Pr { get; init; }
}
96 changes: 96 additions & 0 deletions src/SecTester.Scan/CI/VendorMatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System.Collections;
using System.Linq;
using System.Text.Json;

namespace SecTester.Scan.CI;

internal class VendorMatcher
{
private readonly IDictionary _env;

public VendorMatcher(IDictionary env)
{
_env = env;
}

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


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

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

private bool MatchInnerAny(JsonElement obj)
{
var any = obj.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()!));
}

private bool MatchInnerEnvIncludes(JsonElement obj)
{
var enumerable = obj.EnumerateObject();
var env = enumerable.FirstOrDefault(x => x.NameEquals("env"));

if (env.Value.ValueKind == JsonValueKind.String && env.Value.GetString() is not null &&
_env.Contains(env.Value.GetString()!))
{
var envVarValue = _env[env.Value.GetString()!]?.ToString();

var includes = enumerable.FirstOrDefault(x => x.NameEquals("includes"));

if (includes.Value.ValueKind == JsonValueKind.String && includes.Value.GetString() is not null)
{
return envVarValue?.Contains(includes.Value.GetString()!) ?? false;
}
}

return false;
}

private bool MatchInnerEnvNe(JsonElement obj)
{
var enumerable = obj.EnumerateObject();
var env = enumerable.FirstOrDefault(x => x.NameEquals("env"));

if (env.Value.ValueKind == JsonValueKind.String && env.Value.GetString() is not null &&
_env.Contains(env.Value.GetString()!))
{
var envVarValue = _env[env.Value.GetString()!]?.ToString();

var ne = enumerable.FirstOrDefault(x => x.NameEquals("ne"));

if (ne.Value.ValueKind == JsonValueKind.String && ne.Value.ValueEquals("false"))
{
return !string.IsNullOrEmpty(envVarValue);
}
}

return false;
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/SecTester.Scan/Scan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public async Task Expect(Func<Scan, Task<bool>> predicate, CancellationToken can

using var cancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

cancellationTokenSource.CancelAfter(_options.Timeout);

await ExpectCore(predicate, cancellationTokenSource.Token).ConfigureAwait(false);
Expand Down
2 changes: 1 addition & 1 deletion src/SecTester.Scan/SecTester.Scan.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="CI\Lib\vendors.json" />
<EmbeddedResource Include="CI\vendors.json" />
</ItemGroup>
</Project>
Loading

0 comments on commit 4fd4261

Please sign in to comment.