Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project>
<PropertyGroup>
<Version>0.6.2</Version>
<Product>Tilework</Product>
<Version>0.6.3</Version>
</PropertyGroup>
</Project>
21 changes: 21 additions & 0 deletions tilework.core/AppMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Reflection;

namespace Tilework.Core;

public static class AppMetadata
{
private static readonly Assembly _assembly = typeof(AppMetadata).Assembly;

public static string Name { get; } =
_assembly.GetCustomAttribute<AssemblyProductAttribute>()?.Product
?? _assembly.GetName().Name
?? "Application";

public static string InformationalVersion { get; } =
_assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
?? "0.0.0";

public static string Version { get; } = InformationalVersion.Split('+')[0];

public static string DisplayVersion => $"v{Version}";
}
7 changes: 2 additions & 5 deletions tilework.core/Commands/PrintVersion.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Tilework.Core.Interfaces;
using System.Reflection;

namespace Tilework.Core.Commands;

Expand All @@ -14,9 +13,7 @@ public PrintVersionInfoCommand()

public async Task<int> run(string[] args)
{
var assembly = Assembly.GetExecutingAssembly();
var informationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
Console.WriteLine(informationalVersion);
Console.WriteLine(AppMetadata.InformationalVersion);
return 0;
}
}
}
5 changes: 3 additions & 2 deletions tilework.core/Enums/LoadBalancing/ConditionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum ConditionType
[Description("Query string")]
QueryString,
[Description("SNI FQDN")]
SNI
SNI,
[Description("Source IP")]
SourceIp
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,23 @@ public static class LoadBalancerConditionRules
{
ConditionType.HostHeader,
ConditionType.Path,
ConditionType.QueryString
ConditionType.QueryString,
ConditionType.SourceIp
};

private static readonly ConditionType[] HttpsConditions =
{
ConditionType.HostHeader,
ConditionType.Path,
ConditionType.QueryString,
ConditionType.SNI
ConditionType.SNI,
ConditionType.SourceIp
};

private static readonly ConditionType[] TlsConditions =
{
ConditionType.SNI
ConditionType.SNI,
ConditionType.SourceIp
};

public static IReadOnlyList<ConditionType> GetAllowedConditions(LoadBalancerProtocol protocol)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.ComponentModel;

namespace Tilework.LoadBalancing.Haproxy;

public enum AclCondition
{
HostHeader,
Path,
QueryString,
SNI,
SourceIp,
VariableSet,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.ComponentModel;

namespace Tilework.LoadBalancing.Haproxy;

public enum HttpRequestAction
{
AddHeader,
Redirect,
Return,
Deny,
SetVariable
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,102 +8,28 @@

public class HAProxyConfigurationProfile : Profile
{
private const string BackendVariableName = "txn.tilework_backend";
private const string BackendSelectedAclName = "backend_selected";

public HAProxyConfigurationProfile()
{
CreateMap<ConditionType, AclCondition>()
.ConvertUsing(src => MapToAclCondition(src));

CreateMap<LoadBalancer, FrontendSection>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Id.ToString()))
.ForPath(dest => dest.Bind.Address, opt => opt.MapFrom(src => "*"))
.ForPath(dest => dest.Bind.Port, opt => opt.MapFrom(src => src.Port))
.AfterMap((src, dest) =>
.AfterMap((src, dest, context) =>
{
if (src.Protocol == LoadBalancerProtocol.HTTPS || src.Protocol == LoadBalancerProtocol.TLS)
dest.Bind.EnableTls = true;

if (src.Type == LoadBalancerType.APPLICATION)
{
dest.AddHeaders.Add(new HttpHeader()
{
Name = "X-Forwarded-Proto",
Value = src.Protocol == LoadBalancerProtocol.HTTPS ? "https" : "http"
});

dest.AddHeaders.Add(new HttpHeader()
{
Name = "X-Forwarded-Port",
Value = src.Port.ToString()
});
}

dest.Mode = src.Type == LoadBalancerType.APPLICATION ? Mode.HTTP : Mode.TCP;
if (src.Rules != null)
{
foreach (var rule in src.Rules.OrderBy(r => r.Priority))
{
var acls = new List<Acl>();
for (int i = 0; i < rule.Conditions.Count; i++)
{
var condition = rule.Conditions[i];

var acl = new Acl()
{
Name = $"{rule.Id.ToString()}-{i}",
Type = condition.Type,
Values = condition.Values
};

acls.Add(acl);
}

dest.Acls.AddRange(acls);

if (rule.Action == null)
throw new InvalidOperationException($"Rule {rule.Id} is missing an action.");

var actionType = rule.Action.Type;
switch (actionType)
{
case RuleActionType.Forward:
var targetGroup = rule.TargetGroup;
if (targetGroup != null)
{
var usebe = new UseBackend()
{
Acls = acls.Select(a => a.Name).ToList(),
Target = targetGroup.Id.ToString(),
};
dest.UseBackends.Add(usebe);
}
break;
case RuleActionType.Redirect:
dest.HttpRequests.Add(new HttpRequest()
{
ActionType = RuleActionType.Redirect,
RedirectUrl = rule.Action?.RedirectUrl,
RedirectStatusCode = rule.Action?.RedirectStatusCode,
Acls = acls.Select(a => a.Name).ToList()
});
break;
case RuleActionType.FixedResponse:
dest.HttpRequests.Add(new HttpRequest()
{
ActionType = RuleActionType.FixedResponse,
FixedResponseStatusCode = rule.Action?.FixedResponseStatusCode,
FixedResponseContentType = rule.Action?.FixedResponseContentType,
FixedResponseBody = rule.Action?.FixedResponseBody,
Acls = acls.Select(a => a.Name).ToList()
});
break;
case RuleActionType.Reject:
dest.TcpRequests.Add(new TcpRequest()
{
Acls = acls.Select(a => a.Name).ToList()
});
break;
default:
throw new NotSupportedException($"Unsupported rule action: {actionType}");
}
}
}
if (dest.Mode == Mode.HTTP)
MapHttpLoadBalancerRules(src, dest, context);
else
MapTcpLoadBalancerRules(src, dest, context);
});

CreateMap<LoadBalancer, PortType>()
Expand Down Expand Up @@ -137,4 +63,158 @@
}).ToList();
});
}

private static AclCondition MapToAclCondition(ConditionType conditionType)
{
return conditionType switch
{
ConditionType.HostHeader => AclCondition.HostHeader,
ConditionType.Path => AclCondition.Path,
ConditionType.QueryString => AclCondition.QueryString,
ConditionType.SNI => AclCondition.SNI,
ConditionType.SourceIp => AclCondition.SourceIp,
_ => throw new NotSupportedException($"Unsupported condition type for HAProxy ACL mapping: {conditionType}")
};
}

private static void MapHttpLoadBalancerRules(LoadBalancer src, FrontendSection dest, ResolutionContext context)

Check failure on line 80 in tilework.core/Providers/LoadBalancingProviders/HAProxy/Mappers/HAProxyConfigurationProfile.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 17 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=tileworkdev_tilework&issues=AZ0WcWdDEnrKnygj_-zX&open=AZ0WcWdDEnrKnygj_-zX&pullRequest=71
{
dest.HttpRequests.Add(new AddHeaderHttpRequest(
"X-Forwarded-Proto",
src.Protocol == LoadBalancerProtocol.HTTPS ? "https" : "http"));

dest.HttpRequests.Add(new AddHeaderHttpRequest(
"X-Forwarded-Port",
src.Port.ToString()));

if (src.Rules is not { Count: > 0 })
return;

var hasForwardRule = false;
dest.Acls.Add(new Acl()
{
Name = BackendSelectedAclName,
Type = AclCondition.VariableSet,
Values = new List<string> { BackendVariableName }
});

foreach (var rule in src.Rules.OrderBy(r => r.Priority))
{
var ruleAcls = BuildRuleAcls(rule, context);
dest.Acls.AddRange(ruleAcls);

if (rule.Action == null)
throw new InvalidOperationException($"Rule {rule.Id} is missing an action.");

var gatedAcls = new List<string> { $"!{BackendSelectedAclName}" };
gatedAcls.AddRange(ruleAcls.Select(a => a.Name));

switch (rule.Action.Type)
{
case RuleActionType.Forward:
if (rule.TargetGroup != null)
{
hasForwardRule = true;
dest.HttpRequests.Add(new SetVariableHttpRequest(BackendVariableName, rule.TargetGroup.Id.ToString())
{
Acls = gatedAcls
});
}
break;
case RuleActionType.Redirect:
if (string.IsNullOrWhiteSpace(rule.Action.RedirectUrl))
throw new InvalidOperationException($"Rule {rule.Id} redirect action is missing RedirectUrl.");

dest.HttpRequests.Add(new RedirectHttpRequest(rule.Action.RedirectUrl)
{
StatusCode = rule.Action.RedirectStatusCode,
Acls = gatedAcls
});
break;
case RuleActionType.FixedResponse:
if (rule.Action.FixedResponseStatusCode == null)
throw new InvalidOperationException($"Rule {rule.Id} fixed response action is missing FixedResponseStatusCode.");

dest.HttpRequests.Add(new ReturnHttpRequest(rule.Action.FixedResponseStatusCode.Value)
{
ContentType = rule.Action.FixedResponseContentType,
Body = rule.Action.FixedResponseBody,
Acls = gatedAcls
});
break;
case RuleActionType.Reject:
dest.HttpRequests.Add(new DenyHttpRequest()
{
Acls = gatedAcls
});
break;
default:
throw new NotSupportedException($"Unsupported rule action: {rule.Action.Type}");
}
}

if (hasForwardRule)
{
dest.UseBackends.Add(new UseBackend()
{
Target = $"%[var({BackendVariableName})]",
Acls = new List<string> { BackendSelectedAclName }
});
}
}

private static void MapTcpLoadBalancerRules(LoadBalancer src, FrontendSection dest, ResolutionContext context)
{
if (src.Rules is not { Count: > 0 })
return;

foreach (var rule in src.Rules.OrderBy(r => r.Priority))
{
var ruleAcls = BuildRuleAcls(rule, context);
dest.Acls.AddRange(ruleAcls);

if (rule.Action == null)
throw new InvalidOperationException($"Rule {rule.Id} is missing an action.");

var aclNames = ruleAcls.Select(a => a.Name).ToList();
switch (rule.Action.Type)
{
case RuleActionType.Forward:
if (rule.TargetGroup != null)
{
dest.UseBackends.Add(new UseBackend()
{
Acls = aclNames,
Target = rule.TargetGroup.Id.ToString()
});
}
break;
case RuleActionType.Reject:
dest.TcpRequests.Add(new TcpRequest()
{
Acls = aclNames
});
break;
default:
throw new NotSupportedException($"Unsupported TCP rule action: {rule.Action.Type}");
}
}
}

private static List<Acl> BuildRuleAcls(Rule rule, ResolutionContext context)
{
var ruleAcls = new List<Acl>();
for (int i = 0; i < rule.Conditions.Count; i++)
{
var condition = rule.Conditions[i];
ruleAcls.Add(new Acl()
{
Name = $"{rule.Id}-{i}",
Type = context.Mapper.Map<AclCondition>(condition.Type),
Values = condition.Values
});
}

return ruleAcls;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ public class FrontendSection : ConfigSection
[Statement("default_backend")]
public string DefaultBackend { get; set; }

[Statement("http-request add-header")]
public List<HttpHeader> AddHeaders { get; set; } = new List<HttpHeader>();

public FrontendSection() : base("frontend")
{
}
Expand Down
Loading
Loading