Part 4: Lock It Down
The lab is in the Internal zone with your own devices, so they can all reach each other. In this part you’ll give the lab its own zone, block it from Internal, and let your devices reach one lab server over SSH.
Create a zone
Section titled “Create a zone”Create a Lab zone, and move the lab network into it:
import * as UnifiNetwork from "@alchemy-unifi/network";import * as Config from "effect/Config";import * as Effect from "effect/Effect";
export const Lab = Effect.gen(function* () { const zone = yield* UnifiNetwork.FirewallZone("lab-zone", { name: "Lab" }); const network = yield* UnifiNetwork.Network("lab", { management: "GATEWAY", name: "Lab", vlanId: 50, enabled: true, cellularBackupEnabled: false, internetAccessEnabled: true, isolationEnabled: false, zone: UnifiNetwork.FirewallZone.INTERNAL, zone: zone, ipv4Configuration: { autoScaleEnabled: false, hostIpAddress: "192.168.50.1", prefixLength: 24, dhcpConfiguration: { mode: "SERVER", ipAddressRange: { start: "192.168.50.100", stop: "192.168.50.250" }, leaseTimeSeconds: 86400, pingConflictDetectionEnabled: true, }, }, }); yield* UnifiNetwork.WifiBroadcast("lab-wifi", { type: "STANDARD", name: "Lab", enabled: true, network, securityConfiguration: { type: "WPA2_PERSONAL", passphrase: yield* Config.Redacted("LAB_WIFI_PASSPHRASE"), fastRoamingEnabled: false, }, broadcastingFrequenciesGHz: [2.4, 5], hideName: false, clientIsolationEnabled: false, multicastToUnicastConversionEnabled: false, uapsdEnabled: false, channel2gLockedTo6: false, dtimPeriod2gLockedTo3: false, advertiseDeviceName: false, arpProxyEnabled: false, bssTransitionEnabled: true, }); return { network }; return { network, zone };});Networks join zones from the network’s side, through zone.
Block the lab from your devices
Section titled “Block the lab from your devices”Create src/firewall.ts, with a policy that blocks traffic from the lab to Internal:
import * as UnifiNetwork from "@alchemy-unifi/network";import type { Input } from "alchemy/Input";import * as Effect from "effect/Effect";
export const Firewall = (lab: Input<UnifiNetwork.FirewallZoneReference>) => Effect.gen(function* () { const { INTERNAL } = UnifiNetwork.FirewallZone; yield* UnifiNetwork.FirewallPolicy("block-lab-to-internal", { name: "Block Lab to Internal", enabled: true, action: { type: "BLOCK" }, source: { zone: lab }, destination: { zone: INTERNAL }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" }, loggingEnabled: false, }); });Wire it into the Stack
Section titled “Wire it into the Stack”import * as UnifiNetwork from "@alchemy-unifi/network";import * as Alchemy from "alchemy";import * as RemovalPolicy from "alchemy/RemovalPolicy";import * as Effect from "effect/Effect";import { Firewall } from "./src/firewall.ts";import { Lab } from "./src/lab.ts";
export default Alchemy.Stack( "UnifiLab", { providers: UnifiNetwork.providers(), state: UnifiNetwork.state() }, Effect.gen(function* () { const lab = yield* Lab; yield* Firewall(lab.zone); yield* UnifiNetwork.Network("default", { management: "GATEWAY", name: "Default", vlanId: 1, enabled: true, cellularBackupEnabled: true, internetAccessEnabled: true, isolationEnabled: false, zone: UnifiNetwork.FirewallZone.INTERNAL, ipv4Configuration: { autoScaleEnabled: true, hostIpAddress: "192.168.1.1", prefixLength: 24, dhcpConfiguration: { mode: "SERVER", ipAddressRange: { start: "192.168.1.6", stop: "192.168.1.254" }, leaseTimeSeconds: 86400, domainName: "localdomain", pingConflictDetectionEnabled: true, }, }, }).pipe(RemovalPolicy.retain()); return { labNetworkId: lab.network.networkId }; }),);Name the lab servers
Section titled “Name the lab servers”A traffic-matching list names addresses once, for any number of policies:
import * as UnifiNetwork from "@alchemy-unifi/network";import type { Input } from "alchemy/Input";import * as Effect from "effect/Effect";
export const Firewall = (lab: Input<UnifiNetwork.FirewallZoneReference>) => Effect.gen(function* () { const { INTERNAL } = UnifiNetwork.FirewallZone; const servers = yield* UnifiNetwork.TrafficMatchingList("lab-servers", { type: "IPV4_ADDRESSES", name: "Lab servers", items: [{ type: "IP_ADDRESS", value: "192.168.50.10" }], }); yield* UnifiNetwork.FirewallPolicy("block-lab-to-internal", { name: "Block Lab to Internal", enabled: true, action: { type: "BLOCK" }, source: { zone: lab }, destination: { zone: INTERNAL }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" }, loggingEnabled: false, }); });Allow SSH to the servers
Section titled “Allow SSH to the servers”Allow SSH from Internal to the lab servers, and block everything else from Internal to the lab:
import * as UnifiNetwork from "@alchemy-unifi/network";import type { Input } from "alchemy/Input";import * as Effect from "effect/Effect";
export const Firewall = (lab: Input<UnifiNetwork.FirewallZoneReference>) => Effect.gen(function* () { const { INTERNAL } = UnifiNetwork.FirewallZone; const servers = yield* UnifiNetwork.TrafficMatchingList("lab-servers", { type: "IPV4_ADDRESSES", name: "Lab servers", items: [{ type: "IP_ADDRESS", value: "192.168.50.10" }], }); yield* UnifiNetwork.FirewallPolicy("block-lab-to-internal", { name: "Block Lab to Internal", enabled: true, action: { type: "BLOCK" }, source: { zone: lab }, destination: { zone: INTERNAL }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" }, loggingEnabled: false, }); const allowSsh = yield* UnifiNetwork.FirewallPolicy("allow-ssh-to-lab-servers", { name: "Allow SSH to the lab servers", enabled: true, action: { type: "ALLOW", allowReturnTraffic: true }, source: { zone: INTERNAL }, destination: { zone: lab, trafficFilter: { type: "IP_ADDRESS", ipAddressFilter: { type: "TRAFFIC_MATCHING_LIST", matchOpposite: false, trafficMatchingList: servers }, portFilter: { type: "PORTS", matchOpposite: false, items: [{ type: "PORT_NUMBER", value: 22 }] }, }, }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6", protocolFilter: { type: "PRESET", preset: { name: "TCP_UDP" } }, }, loggingEnabled: false, }); const blockRest = yield* UnifiNetwork.FirewallPolicy("block-internal-to-lab", { name: "Block Internal to the rest of the lab", enabled: true, action: { type: "BLOCK" }, source: { zone: INTERNAL }, destination: { zone: lab }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" }, loggingEnabled: false, }); });allowReturnTraffic: true lets the replies back, so the SSH session works without a policy the other way.
Put them in order
Section titled “Put them in order”Policies run in order, first match wins, and new ones go last. Put the allow before the block:
import * as UnifiNetwork from "@alchemy-unifi/network";import type { Input } from "alchemy/Input";import * as Effect from "effect/Effect";
export const Firewall = (lab: Input<UnifiNetwork.FirewallZoneReference>) => Effect.gen(function* () { const { INTERNAL } = UnifiNetwork.FirewallZone; const servers = yield* UnifiNetwork.TrafficMatchingList("lab-servers", { type: "IPV4_ADDRESSES", name: "Lab servers", items: [{ type: "IP_ADDRESS", value: "192.168.50.10" }], }); yield* UnifiNetwork.FirewallPolicy("block-lab-to-internal", { name: "Block Lab to Internal", enabled: true, action: { type: "BLOCK" }, source: { zone: lab }, destination: { zone: INTERNAL }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" }, loggingEnabled: false, }); const allowSsh = yield* UnifiNetwork.FirewallPolicy("allow-ssh-to-lab-servers", { name: "Allow SSH to the lab servers", enabled: true, action: { type: "ALLOW", allowReturnTraffic: true }, source: { zone: INTERNAL }, destination: { zone: lab, trafficFilter: { type: "IP_ADDRESS", ipAddressFilter: { type: "TRAFFIC_MATCHING_LIST", matchOpposite: false, trafficMatchingList: servers }, portFilter: { type: "PORTS", matchOpposite: false, items: [{ type: "PORT_NUMBER", value: 22 }] }, }, }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6", protocolFilter: { type: "PRESET", preset: { name: "TCP_UDP" } }, }, loggingEnabled: false, }); const blockRest = yield* UnifiNetwork.FirewallPolicy("block-internal-to-lab", { name: "Block Internal to the rest of the lab", enabled: true, action: { type: "BLOCK" }, source: { zone: INTERNAL }, destination: { zone: lab }, ipProtocolScope: { ipVersion: "IPV4_AND_IPV6" }, loggingEnabled: false, }); yield* UnifiNetwork.FirewallPolicyOrdering("internal-to-lab", { source: INTERNAL, destination: lab, beforeSystemDefined: [allowSsh, blockRest], }); });Deploy
Section titled “Deploy”npx alchemy deploy --stage homeTry it out
Section titled “Try it out”From your laptop on the default network, ssh to 192.168.50.10 connects, if you have a server there. Anything else in the lab is unreachable, and a phone on the Lab SSID can’t reach your laptop.
You put the lab in its own zone and wrote its firewall rules. Next, in Part 5, you’ll give the lab server a name.