这是indexloc提供的服务,不要输入任何密码
Skip to content

[Firewall Config] Support values in conditions #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 17, 2025
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
2 changes: 1 addition & 1 deletion client/firewall_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Condition struct {
Op string `json:"op"`
Neg bool `json:"neg"`
Key string `json:"key"`
Value string `json:"value"`
Value any `json:"value"`
}

type Action struct {
Expand Down
33 changes: 31 additions & 2 deletions docs/resources/firewall_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,34 @@ resource "vercel_firewall_config" "example" {
action_duration = "5m"
}
}

rule {
name = "Known clients"
description = "Match known keys in header
condition_group = [{
conditions = [{
type = "header"
key = "Authorization"
op = "inc"
values = [
"key1",
"key2",
]
}]
}]

action = {
action = "rate_limit"
rate_limit = {
limit = 100
window = 300
keys = ["ip", "ja4"]
algo = "fixed_window"
action = "deny"
}
action_duration = "5m"
}
}
}
}

Expand Down Expand Up @@ -420,8 +448,9 @@ Required:
Optional:

- `key` (String) Key within type to match against
- `neg` (Boolean)
- `value` (String)
- `neg` (Boolean) Negate the condition
- `value` (String) Value to match against
- `values` (List of String) Values to match against if op is inc, ninc

## Import

Expand Down
28 changes: 28 additions & 0 deletions examples/resources/vercel_firewall_config/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,34 @@ resource "vercel_firewall_config" "example" {
action_duration = "5m"
}
}

rule {
name = "Known clients"
description = "Match known keys in header
condition_group = [{
conditions = [{
type = "header"
key = "Authorization"
op = "inc"
values = [
"key1",
"key2",
]
}]
}]

action = {
action = "rate_limit"
rate_limit = {
limit = 100
window = 300
keys = ["ip", "ja4"]
algo = "fixed_window"
action = "deny"
}
action_duration = "5m"
}
}
}
}

Expand Down
119 changes: 91 additions & 28 deletions vercel/resource_firewall_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
Expand Down Expand Up @@ -320,14 +321,33 @@ Define Custom Rules to shape the way your traffic is handled by the Vercel Edge
},
},
"neg": schema.BoolAttribute{
Optional: true,
Description: "Negate the condition",
Optional: true,
},
"key": schema.StringAttribute{
Description: "Key within type to match against",
Optional: true,
},
"value": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.ConflictsWith(
path.MatchRelative().AtParent().AtName("values"),
path.MatchRelative().AtParent().AtName("value"),
),
},
Description: "Value to match against",
Optional: true,
},
"values": schema.ListAttribute{
Validators: []validator.List{
listvalidator.ConflictsWith(
path.MatchRelative().AtParent().AtName("value"),
path.MatchRelative().AtParent().AtName("values"),
),
},
ElementType: types.StringType,
Description: "Values to match against if op is inc, ninc",
Optional: true,
},
},
},
Expand Down Expand Up @@ -472,24 +492,41 @@ type FirewallRule struct {
Action Mitigate `tfsdk:"action"`
}

func (r *FirewallRule) Conditions() []client.ConditionGroup {
func isListOp(op string) bool {
return op == "inc" || op == "ninc"
}

func (r *FirewallRule) Conditions() ([]client.ConditionGroup, error) {
var groups []client.ConditionGroup
for _, group := range r.ConditionGroup {
for cgIndex, group := range r.ConditionGroup {
var conditions []client.Condition
for _, condition := range group.Conditions {
conditions = append(conditions, client.Condition{
Type: condition.Type.ValueString(),
Op: condition.Op.ValueString(),
Neg: condition.Neg.ValueBool(),
Key: condition.Key.ValueString(),
Value: condition.Value.ValueString(),
})
for condIndex, condition := range group.Conditions {
cond := client.Condition{
Type: condition.Type.ValueString(),
Op: condition.Op.ValueString(),
Neg: condition.Neg.ValueBool(),
Key: condition.Key.ValueString(),
}
if isListOp(condition.Op.ValueString()) {
if condition.Values.IsNull() {
return nil, fmt.Errorf("rule %s conditionGroup.%d.condition.%d, operator requires list values", r.Name.ValueString(), cgIndex, condIndex)
}
vals := make([]string, len(condition.Values.Elements()))
condition.Values.ElementsAs(context.Background(), &vals, false)
cond.Value = vals
} else {
if !condition.Values.IsNull() {
return nil, fmt.Errorf("rule %s conditionGroup.%d.condition.%d, operator does not allow values", r.Name.ValueString(), cgIndex, condIndex)
}
cond.Value = condition.Value.ValueString()
}
conditions = append(conditions, cond)
}
groups = append(groups, client.ConditionGroup{
Conditions: conditions,
})
}
return groups
return groups, nil
}

func (r *FirewallRule) Mitigate() (client.Mitigate, error) {
Expand Down Expand Up @@ -549,7 +586,10 @@ func fromFirewallRule(rule client.FirewallRule, ref FirewallRule) (FirewallRule,
if len(ref.ConditionGroup) > j && len(ref.ConditionGroup[j].Conditions) > k {
cond = ref.ConditionGroup[j].Conditions[k]
}
conditions[k] = fromCondition(condition, cond)
conditions[k], err = fromCondition(condition, cond)
if err != nil {
return r, err
}
}
conditionGroups[j] = ConditionGroup{
Conditions: conditions,
Expand Down Expand Up @@ -651,21 +691,39 @@ type ConditionGroup struct {
}

type Condition struct {
Type types.String `tfsdk:"type"`
Op types.String `tfsdk:"op"`
Neg types.Bool `tfsdk:"neg"`
Key types.String `tfsdk:"key"`
Value types.String `tfsdk:"value"`
Type types.String `tfsdk:"type"`
Op types.String `tfsdk:"op"`
Neg types.Bool `tfsdk:"neg"`
Key types.String `tfsdk:"key"`
Value types.String `tfsdk:"value"`
Values types.List `tfsdk:"values"`
}

func fromCondition(condition client.Condition, ref Condition) Condition {
func fromCondition(condition client.Condition, ref Condition) (Condition, error) {
c := Condition{
Type: types.StringValue(condition.Type),
Op: types.StringValue(condition.Op),
Value: types.StringValue(condition.Value),
Key: types.StringValue(condition.Key),
Neg: types.BoolValue(condition.Neg),
Type: types.StringValue(condition.Type),
Op: types.StringValue(condition.Op),
Key: types.StringValue(condition.Key),
Neg: types.BoolValue(condition.Neg),
Value: types.StringNull(),
Values: types.ListNull(types.StringType),
}
if isListOp(condition.Op) {
if valueList, ok := condition.Value.([]interface{}); ok {
values, diags := basetypes.NewListValueFrom(context.Background(), types.StringType, valueList)
if diags.HasError() {
return c, fmt.Errorf("error converting values: %s - %s", diags[0].Summary(), diags[0].Detail())
}
c.Values = values
} else {
return c, fmt.Errorf("condition value is not a list")

}
} else {
val := condition.Value.(string)
c.Value = types.StringValue(val)
}

// Neg and Key are optional
if ref.Neg == types.BoolNull() {
c.Neg = types.BoolNull()
Expand All @@ -678,7 +736,7 @@ func fromCondition(condition client.Condition, ref Condition) Condition {
c.Value = types.StringNull()
}
}
return c
return c, nil
}

type IPRules struct {
Expand Down Expand Up @@ -819,12 +877,16 @@ func (f *FirewallConfig) toClient() (client.FirewallConfig, error) {
if err != nil {
return conf, err
}
condGroup, err := rule.Conditions()
if err != nil {
return conf, err
}
conf.Rules = append(conf.Rules, client.FirewallRule{
ID: rule.ID.ValueString(),
Name: rule.Name.ValueString(),
Description: rule.Description.ValueString(),
Active: rule.Active.IsNull() || rule.Active.ValueBool(),
ConditionGroup: rule.Conditions(),
ConditionGroup: condGroup,
Action: client.Action{
Mitigate: mit,
},
Expand All @@ -847,7 +909,6 @@ func (f *FirewallConfig) toClient() (client.FirewallConfig, error) {
}

func (r *firewallConfigResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {

var plan FirewallConfig
diags := req.Plan.Get(ctx, &plan)
if resp.Diagnostics.HasError() {
Expand All @@ -857,6 +918,7 @@ func (r *firewallConfigResource) Create(ctx context.Context, req resource.Create
conf, err := plan.toClient()
if err != nil {
diags.AddError("failed to convert plan to client", err.Error())
resp.Diagnostics.Append(diags...)
return
}

Expand Down Expand Up @@ -914,6 +976,7 @@ func (r *firewallConfigResource) Update(ctx context.Context, req resource.Update
conf, err := plan.toClient()
if err != nil {
diags.AddError("failed to convert plan to client", err.Error())
resp.Diagnostics.Append(diags...)
return
}

Expand Down
42 changes: 42 additions & 0 deletions vercel/resource_firewall_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ func TestAcc_FirewallConfigResource(t *testing.T) {
"vercel_firewall_config.custom",
"rules.rule.2.action.redirect.permanent",
"false"),
resource.TestCheckResourceAttr(
"vercel_firewall_config.custom",
"rules.rule.4.condition_group.0.conditions.0.values.0",
"/test1"),
resource.TestCheckResourceAttr(
"vercel_firewall_config.custom",
"rules.rule.4.condition_group.0.conditions.0.values.1",
"/test2"),
resource.TestCheckResourceAttr(
"vercel_firewall_config.ips",
"ip_rules.rule.0.action",
Expand Down Expand Up @@ -387,6 +395,23 @@ resource "vercel_firewall_config" "custom" {
}]
}]
}
rule {
name = "test_list"
action = {
action = "deny"
}
condition_group = [{
conditions = [{
type = "path"
op = "inc"
values = [
"/test1",
"/test2",
"/test3"
]
}]
}]
}
}
}

Expand Down Expand Up @@ -510,6 +535,23 @@ resource "vercel_firewall_config" "custom" {
}]
}]
}
rule {
name = "test_list"
action = {
action = "deny"
}
condition_group = [{
conditions = [{
type = "path"
op = "inc"
values = [
"/api",
"/api2",
"/api3"
]
}]
}]
}
}
}

Expand Down
Loading