This avoids the separate exception handling for web requests. The api request handling is better here as it shows an error for the specific field. It's also simpler. You can only hit an error in the web case if the attached subnets are modified after the page was displayed, or the user is manually changing them.
397 lines
14 KiB
Ruby
397 lines
14 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "spec_helper"
|
|
|
|
RSpec.describe Clover, "firewall" do
|
|
let(:user) { create_account }
|
|
|
|
let(:project) { user.create_project_with_default_policy("project-1") }
|
|
|
|
let(:project_wo_permissions) { user.create_project_with_default_policy("project-2", default_policy: nil) }
|
|
|
|
let(:firewall) do
|
|
Firewall.create(name: "dummy-fw", description: "dummy-fw", location_id: Location::HETZNER_FSN1_ID, project_id: project.id)
|
|
end
|
|
|
|
let(:fw_wo_permission) {
|
|
Firewall.create(name: "dummy-fw-2", description: "dummy-fw-2", location_id: Location::HETZNER_FSN1_ID, project_id: project_wo_permissions.id)
|
|
}
|
|
|
|
describe "unauthenticated" do
|
|
it "can not list without login" do
|
|
visit "/firewall"
|
|
|
|
expect(page.title).to eq("Ubicloud - Login")
|
|
end
|
|
|
|
it "can not create without login" do
|
|
visit "/firewall/create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Login")
|
|
end
|
|
end
|
|
|
|
describe "authenticated" do
|
|
before do
|
|
login(user.email)
|
|
end
|
|
|
|
describe "list" do
|
|
it "can list no firewalls" do
|
|
visit "#{project.path}/firewall"
|
|
|
|
expect(page.title).to eq("Ubicloud - Firewalls")
|
|
expect(page).to have_content "No firewalls"
|
|
|
|
click_link "Create Firewall"
|
|
expect(page.title).to eq("Ubicloud - Create Firewall")
|
|
end
|
|
|
|
it "can not list firewalls when does not have permissions" do
|
|
firewall
|
|
fw_wo_permission
|
|
visit "#{project.path}/firewall"
|
|
|
|
expect(page.title).to eq("Ubicloud - Firewalls")
|
|
expect(page).to have_content firewall.name
|
|
expect(page).to have_no_content fw_wo_permission.name
|
|
end
|
|
|
|
it "does not show links to firewalls if user lacks Firewall:view access to them" do
|
|
firewall
|
|
fw = Firewall.create(name: "viewable-fw", description: "viewable-fw", location_id: Location::HETZNER_FSN1_ID, project_id: project.id)
|
|
|
|
visit "#{project.path}/firewall"
|
|
link_texts = page.all("a").map(&:text)
|
|
expect(link_texts).to include fw.name
|
|
expect(link_texts).to include firewall.name
|
|
expect(link_texts).to include "Create Firewall"
|
|
|
|
AccessControlEntry.dataset.destroy
|
|
AccessControlEntry.create(project_id: project.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Firewall:view"], object_id: fw.id)
|
|
page.refresh
|
|
expect(page).to have_no_content firewall.name
|
|
link_texts = page.all("a").map(&:text)
|
|
expect(link_texts).to include fw.name
|
|
expect(link_texts).not_to include "Create Firewall"
|
|
|
|
click_link fw.name
|
|
expect(page).to have_no_content "Delete firewall"
|
|
expect(page.body).not_to include "form-fw-create-rule"
|
|
|
|
fw.add_firewall_rule(cidr: "127.0.0.1")
|
|
fw.add_private_subnet(net6: "::0/24", net4: "127.0.0.0/24", name: "dummy-ps", location_id: Location[name: "hetzner-hel1"].id, project_id: project.id)
|
|
|
|
page.refresh
|
|
expect(page.body).not_to include "private_subnet_id"
|
|
expect(page.body).not_to include "/detach-subnet\""
|
|
expect(page.body).not_to include "form-fw-create-rule-"
|
|
expect(page.body).not_to include "/firewall-rule/"
|
|
end
|
|
|
|
it "only shows New Firewall link on empty page if user has Firewall:create access" do
|
|
visit "#{project.path}/firewall"
|
|
expect(page.all("a").map(&:text)).to include "Create Firewall"
|
|
expect(page).to have_content "Get started by creating a new firewall."
|
|
expect(page).to have_no_content "You don't have permission to create firewalls."
|
|
|
|
AccessControlEntry.dataset.destroy
|
|
page.refresh
|
|
expect(page.all("a").map(&:text)).not_to include "Create Firewall"
|
|
expect(page).to have_content "You don't have permission to create firewalls."
|
|
expect(page).to have_no_content "Get started by creating a new firewall."
|
|
end
|
|
end
|
|
|
|
describe "create" do
|
|
it "can create new firewall" do
|
|
project
|
|
visit "#{project.path}/firewall/create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Create Firewall")
|
|
name = "dummy-fw"
|
|
fill_in "Name", with: name
|
|
fill_in "Description", with: name
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{name}")
|
|
expect(page).to have_flash_notice("'#{name}' is created")
|
|
expect(Firewall.count).to eq(1)
|
|
expect(Firewall.first.project_id).to eq(project.id)
|
|
end
|
|
|
|
it "can create new firewall with private subnet" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: Location::HETZNER_FSN1_ID).subject
|
|
|
|
visit "#{project.path}/firewall/create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Create Firewall")
|
|
name = "dummy-fw-1"
|
|
fill_in "Name", with: name
|
|
fill_in "Description", with: name
|
|
select ps.name, from: "private_subnet_id"
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{name}")
|
|
expect(page).to have_flash_notice("'#{name}' is created")
|
|
fw = Firewall[name: name]
|
|
expect(fw.private_subnets.first.id).to eq(ps.id)
|
|
|
|
visit "#{project.path}#{ps.path}"
|
|
expect(page).to have_content name
|
|
|
|
visit "#{project.path}#{fw.path}"
|
|
expect(page).to have_content ps.name
|
|
end
|
|
|
|
it "can not create firewall with invalid name" do
|
|
project
|
|
visit "#{project.path}/firewall/create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Create Firewall")
|
|
|
|
fill_in "Name", with: "invalid name"
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Create Firewall")
|
|
expect(page).to have_content "Name must only contain"
|
|
expect((find "input[name=name]")["value"]).to eq("invalid name")
|
|
end
|
|
|
|
it "can not create firewall in a project when does not have permissions" do
|
|
project_wo_permissions
|
|
visit "#{project_wo_permissions.path}/firewall/create"
|
|
|
|
expect(page.title).to eq("Ubicloud - Forbidden")
|
|
expect(page.status_code).to eq(403)
|
|
expect(page).to have_content "Forbidden"
|
|
end
|
|
|
|
it "cannot create firewall when location not exist" do
|
|
visit "#{project.path}/firewall/create"
|
|
|
|
fill_in "Name", with: "dummy-fw"
|
|
choose option: Location::HETZNER_FSN1_UBID
|
|
Location[Location::HETZNER_FSN1_ID].destroy
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - ResourceNotFound")
|
|
expect(page.status_code).to eq(404)
|
|
expect(page).to have_content "ResourceNotFound"
|
|
end
|
|
end
|
|
|
|
describe "show" do
|
|
it "can show firewall details" do
|
|
firewall
|
|
visit "#{project.path}/firewall"
|
|
|
|
expect(page.title).to eq("Ubicloud - Firewalls")
|
|
expect(page).to have_content firewall.name
|
|
|
|
click_link firewall.name, href: "#{project.path}#{firewall.path}"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_content firewall.name
|
|
end
|
|
|
|
it "raises forbidden when does not have permissions" do
|
|
visit "#{project_wo_permissions.path}#{fw_wo_permission.path}"
|
|
|
|
expect(page.title).to eq("Ubicloud - Forbidden")
|
|
expect(page.status_code).to eq(403)
|
|
expect(page).to have_content "Forbidden"
|
|
end
|
|
|
|
it "raises not found when firewall not exists" do
|
|
visit "#{project.path}/location/eu-central-h1/firewall/08s56d4kaj94xsmrnf5v5m3mav"
|
|
|
|
expect(page.title).to eq("Ubicloud - ResourceNotFound")
|
|
expect(page.status_code).to eq(404)
|
|
expect(page).to have_content "ResourceNotFound"
|
|
end
|
|
end
|
|
|
|
describe "subnets" do
|
|
it "can show" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: Location::HETZNER_FSN1_ID).subject
|
|
firewall.associate_with_private_subnet(ps)
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_content ps.name
|
|
end
|
|
|
|
it "can attach subnet" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: Location::HETZNER_FSN1_ID).subject
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
select ps.name, from: "private_subnet_id"
|
|
click_button "Attach"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_flash_notice("Private subnet #{ps.name} is attached to the firewall")
|
|
expect(firewall.private_subnets_dataset.count).to eq(1)
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
expect(page).to have_content ps.name
|
|
|
|
visit "#{project.path}#{ps.path}"
|
|
expect(page).to have_content firewall.name
|
|
end
|
|
|
|
it "can not attach subnet when it does not exist" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: Location::HETZNER_FSN1_ID).subject
|
|
visit "#{project.path}#{firewall.path}"
|
|
select "dummy-ps-1", from: "private_subnet_id"
|
|
ps.destroy
|
|
click_button "Attach"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_flash_error("Validation failed for following fields: private_subnet_id")
|
|
expect(page).to have_content("Private subnet with the given id \"#{ps.ubid}\" and the location \"eu-central-h1\" is not found")
|
|
expect(firewall.private_subnets_dataset.count).to eq(0)
|
|
end
|
|
|
|
it "can detach subnet" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1111", location_id: Location::HETZNER_FSN1_ID).subject
|
|
expect(page).to have_no_content ps.name
|
|
|
|
firewall.associate_with_private_subnet(ps)
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
click_button "Detach"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_flash_notice("Private subnet #{ps.name} is detached from the firewall")
|
|
expect(firewall.private_subnets_dataset.count).to eq(0)
|
|
|
|
visit "#{project.path}#{ps.path}"
|
|
expect(page).to have_no_content firewall.name
|
|
end
|
|
|
|
it "can not detach subnet when it does not exist" do
|
|
ps = Prog::Vnet::SubnetNexus.assemble(project.id, name: "dummy-ps-1", location_id: Location::HETZNER_FSN1_ID).subject
|
|
visit "#{project.path}#{firewall.path}"
|
|
select "dummy-ps-1", from: "private_subnet_id"
|
|
click_button "Attach"
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
ps.destroy
|
|
expect(firewall.private_subnets_dataset.count).to eq(0)
|
|
click_button "Detach"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_flash_error("Validation failed for following fields: private_subnet_id")
|
|
expect(page).to have_content("Private subnet with the given id \"#{ps.ubid}\" and the location \"eu-central-h1\" is not found")
|
|
expect(firewall.private_subnets_dataset.count).to eq(0)
|
|
end
|
|
end
|
|
|
|
describe "rules" do
|
|
it "can add" do
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
fill_in "cidr", with: "1.1.1.1/8"
|
|
fill_in "port_range", with: "80"
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_flash_notice("Firewall rule is created")
|
|
expect(firewall.firewall_rules_dataset.count).to eq(1)
|
|
end
|
|
|
|
it "can not add rule when it is invalid" do
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
fill_in "cidr", with: "invalid"
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_content "Invalid CIDR"
|
|
|
|
fill_in "cidr", with: "1.1.1.1/32"
|
|
fill_in "port_range", with: "invalid"
|
|
|
|
click_button "Create"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_content "Invalid port range"
|
|
|
|
expect(firewall.firewall_rules_dataset.count).to eq(0)
|
|
end
|
|
|
|
it "can delete rule" do
|
|
firewall.insert_firewall_rule("1.0.0.0/8", Sequel.pg_range(80..80))
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
btn = find "#fwr-delete-#{firewall.firewall_rules.first.ubid} .delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
|
|
expect(page.body).to eq({message: "Firewall rule deleted"}.to_json)
|
|
expect(firewall.firewall_rules_dataset.count).to eq(0)
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
expect(page).to have_no_content "1.0.0.0/8"
|
|
end
|
|
|
|
it "accepts delete rule if it's already deleted" do
|
|
firewall.insert_firewall_rule("1.0.0.0/8", Sequel.pg_range(80..80))
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
firewall.remove_firewall_rule(firewall.firewall_rules.first)
|
|
btn = find "#fwr-delete-#{firewall.firewall_rules.first.ubid} .delete-btn"
|
|
expect { page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]} }.not_to raise_error
|
|
|
|
expect(firewall.firewall_rules_dataset.count).to eq(0)
|
|
end
|
|
|
|
it "can show firewall rules which have port_range nil" do
|
|
firewall.insert_firewall_rule("1.0.0.0/8", nil)
|
|
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
expect(page).to have_content "1.0.0.0/8"
|
|
expect(page).to have_content "0..65535"
|
|
|
|
expect(firewall.firewall_rules_dataset.count).to eq(1)
|
|
end
|
|
end
|
|
|
|
describe "delete" do
|
|
it "can delete firewall" do
|
|
visit "#{project.path}#{firewall.path}"
|
|
|
|
# We send delete request manually instead of just clicking to button because delete action triggered by JavaScript.
|
|
# UI tests run without a JavaScript enginer.
|
|
btn = find ".delete-btn"
|
|
page.driver.delete btn["data-url"], {_csrf: btn["data-csrf"]}
|
|
|
|
expect(page.status_code).to eq(204)
|
|
expect(page.body).to be_empty
|
|
expect(Firewall.count).to eq(0)
|
|
end
|
|
|
|
it "can not delete firewall when does not have permissions" do
|
|
# Give permission to view, so we can see the detail page
|
|
AccessControlEntry.create(project_id: project_wo_permissions.id, subject_id: user.id, action_id: ActionType::NAME_MAP["Firewall:view"])
|
|
|
|
visit "#{project_wo_permissions.path}#{fw_wo_permission.path}"
|
|
expect(page.title).to eq "Ubicloud - dummy-fw-2"
|
|
|
|
expect { find ".delete-btn" }.to raise_error Capybara::ElementNotFound
|
|
end
|
|
end
|
|
end
|
|
end
|