We are basically updating the location references everywhere with a location id and adding the location relationship to the models to be able to fetch location names when needed. This also makes the LocationNameConverter model obsolete, so we are removing it. Use model id as value for Sequel::Model in resource creation form Use id of the location as preselected value in Postgres update form
395 lines
14 KiB
Ruby
395 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_with_id(name: "dummy-fw", description: "dummy-fw", location_id: Location::HETZNER_FSN1_ID, project_id: project.id)
|
|
end
|
|
|
|
let(:fw_wo_permission) {
|
|
Firewall.create_with_id(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_with_id(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_ID
|
|
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("Private subnet 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}")
|
|
expect(firewall.private_subnets_dataset.count).to eq(1)
|
|
ps.destroy
|
|
click_button "Detach"
|
|
|
|
expect(page.title).to eq("Ubicloud - #{firewall.name}")
|
|
expect(page).to have_flash_error("Private subnet 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_with_id(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
|