Class: Mt::Wall::DSL::RootBuilder

Inherits:
Object
  • Object
show all
Includes:
PolicyScope, RuleScope
Defined in:
lib/mt/wall/dsl/root_builder.rb

Overview

Top-level DSL context. The public methods here are the verbs users write in firewall config files / blocks. Each verb validates its arguments and records a Model::* value object on the Configuration.

‘rule` (Layer-A access grants) comes from RuleScope; `policy` (chain defaults) comes from PolicyScope, shared with DeviceBuilder so the same syntax sets a global default here and overrides it inside a `device` block. The `device` block itself configures Layer-B box firewall and does NOT take `rule`.

Target DSL (not yet implemented):

host "web" do
  address "10.0.0.5"
  address "10.0.1.0/24"
  member_of "web-prod", "web-wordpress"
end
host "db", address: "10.0.2.10"   # one-line shorthand
host "web", "web-prod", "web-wordpress",
     address: ["10.0.0.5", "10.0.1.0/24"]                     # + groups

group "frontend" do
  member "web"
end

service "mysql", protocol: :tcp, ports: [3306]

rule "frontend" do              # grants grouped by source
  to "db", "mysql"              # allow (default)
  to "db", "mysql", :deny
end

policy :forward, :drop          # global default

device "edge-1", host: "10.0.0.1", transport: :rest_api do
  policy :input, :drop          # override for this box
  input do                      # the box's own firewall
    allow_established
    drop_invalid
    accept protocol: :tcp, dst_port: 22, src: "admin"
  end
end

Instance Method Summary collapse

Methods included from PolicyScope

#policy

Methods included from RuleScope

#rule

Constructor Details

#initialize(configuration) ⇒ RootBuilder

Returns a new instance of RootBuilder.



52
53
54
# File 'lib/mt/wall/dsl/root_builder.rb', line 52

def initialize(configuration)
  @configuration = configuration
end

Instance Method Details

#device(name, host:, transport: :rest_api, **options) { ... } ⇒ void

This method returns an undefined value.

Yields:

  • a DeviceBuilder context (per-device policies and rules)



122
123
124
125
126
# File 'lib/mt/wall/dsl/root_builder.rb', line 122

def device(name, host:, transport: :rest_api, **options, &block)
  builder = DeviceBuilder.new(name, host: host, transport: transport, **options)
  builder.instance_eval(&block) if block
  @configuration.add_device(builder.to_device)
end

#group(name, comment: nil) { ... } ⇒ void

This method returns an undefined value.

Yields:

  • a GroupBuilder context (declares members)



97
98
99
100
101
102
# File 'lib/mt/wall/dsl/root_builder.rb', line 97

def group(name, comment: nil, &block)
  builder = GroupBuilder.new(name, comment: comment)
  builder.instance_eval(&block) if block
  group = builder.to_group
  @configuration.declare_group(group.name, group.members, group.comment)
end

#host(name, *groups, address: nil, comment: nil) { ... } ⇒ void

This method returns an undefined value.

A named address object (host). Trailing positional args after the name are group names this host joins (same as ‘member_of` in the block form); addresses come from the `address:` shorthand (a single string or an array, normalized via Array()) or an `address` block.

AUTO-CREATE is NARROW: a group referenced HERE (host-side membership) is created on demand, because the host contributes real addresses to it. Groups referenced from ‘rule` / `src:` / `dst:` are NOT auto-created — an empty/undeclared group there is a fail-fast error in the Compiler.

FAIL-FAST validation:

* a positional `groups` token that parses as an IP/CIDR via IPAddr
  raises ConfigurationError ("looks like an address — did you mean
  address:?") — guards the common `host "web", "10.0.0.5"` slip;
* a host that ends up with NO addresses raises ConfigurationError;
* `name` and every group token must match `\A[\w.-]+\z`.

host "web", "web-prod", address: "10.0.0.5"
host "web", "web-prod", address: ["10.0.0.5", "10.0.1.0/24"]

Parameters:

  • groups (Array<String>)

    group names to fold this host into

Yields:

  • a HostBuilder context



80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/mt/wall/dsl/root_builder.rb', line 80

def host(name, *groups, address: nil, comment: nil, &block)
  memberships = groups.map { |token| Validators.validate_group_token!(token) }

  builder = HostBuilder.new(name, comment: comment)
  builder.instance_eval(&block) if block
  Array(address).each { |entry| builder.address(entry) }

  object = builder.to_object
  @configuration.add_object(object)

  (memberships + builder.memberships).uniq.each do |group_name|
    @configuration.add_membership(object.name, group_name)
  end
end

#record_policy(policy) ⇒ void

This method returns an undefined value.



135
136
137
# File 'lib/mt/wall/dsl/root_builder.rb', line 135

def record_policy(policy)
  @configuration.add_global_policy(policy)
end

#record_rule(rule) ⇒ void

This method returns an undefined value.

RuleScope storage hooks (record onto the Configuration).



130
131
132
# File 'lib/mt/wall/dsl/root_builder.rb', line 130

def record_rule(rule)
  @configuration.add_rule(rule)
end

#service(name, protocol: nil, protocols: nil, ports: []) ⇒ void

This method returns an undefined value.

A named protocol/port definition. Accepts a single ‘protocol:` (legacy) OR multiple `protocols:` (e.g. DNS = tcp+udp). Ports keep their spec form (Integer/Array/Range/“a-b”), so a range round-trips as a range.

Raises:



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/mt/wall/dsl/root_builder.rb', line 108

def service(name, protocol: nil, protocols: nil, ports: [])
  list = protocols || protocol
  raise ConfigurationError, "service #{name.inspect} needs protocol: or protocols:" if list.nil?

  validated = Array(list).map { |proto| Validators.validate_protocol!(proto) }
  @configuration.add_service(
    Model::Service.new(name: Validators.validate_name!(name, label: "service"),
                       protocols: validated,
                       ports: Validators.validate_ports!(ports))
  )
end