Class: Shells::PfShellWrapper

Inherits:
Object
  • Object
show all
Defined in:
lib/shells/pf_shell_wrapper.rb

Overview

A wrapper class around a base shell to execute pfSense PHP commands within.

Constant Summary collapse

PF_SHELL =

The pfSense shell itself.

'/usr/local/sbin/pfSsh.php'
PF_PROMPT =

The prompt in the pfSense shell.

'pfSense shell:'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_shell, &block) ⇒ PfShellWrapper

Creates the wrapper, executing the pfSense shell.

The provided code block is yielded this wrapper for execution.

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/shells/pf_shell_wrapper.rb', line 30

def initialize(base_shell, &block)
  raise ArgumentError, 'a code block is required' unless block_given?
  raise ArgumentError, 'the base shell must be a valid shell' unless base_shell.is_a?(::Shells::ShellBase)

  self.shell = base_shell

  wrapper = self
  code_block = block
  self.output = ''
  self.config_parsed = false

  shell.instance_eval do
    merge_local_buffer do
      begin
        temporary_prompt(PF_PROMPT) do
          debug 'Initializing the pfSense PHP shell...'
          queue_input PF_SHELL + line_ending
          wait_for_prompt 999, 10, true

          debug ' > initialized'
          begin
            code_block.call wrapper
          ensure
            debug 'Exiting the pfSense PHP shell...'
            if wait_for_prompt(5, 5, false)
              # only queue the exit command if we are still in the pfSense shell.
              queue_input 'exit' + line_ending
            end
          end
        end
      ensure
        # wait for the normal shell to return.
        wait_for_prompt 10, 10, true
        debug ' > exited'
        wrapper.output = output
      end
    end
  end
end

Instance Attribute Details

#outputObject

Gets the output from the pfSense PHP shell session.



24
25
26
# File 'lib/shells/pf_shell_wrapper.rb', line 24

def output
  @output
end

Instance Method Details

#apply_filter_configObject

Apply the firewall configuration.

You need to apply the firewall configuration after you make changes to aliases, NAT rules, or filter rules.



123
124
125
126
127
128
129
# File 'lib/shells/pf_shell_wrapper.rb', line 123

def apply_filter_config
  exec(
      'require_once("shaper.inc");',
      'require_once("filter.inc");',
      'filter_configure_sync();'
  )
end

#apply_user_config(user_id) ⇒ Object

Applies the user configuration for the specified user.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/shells/pf_shell_wrapper.rb', line 133

def apply_user_config(user_id)
  user_id = user_id.to_i
  exec(
      'require_once("auth.inc");',
      "$user_entry = $config[\"system\"][\"user\"][#{user_id}];",
      '$user_groups = array();',
      'foreach ($config["system"]["group"] as $gidx => $group) {',
      '  if (is_array($group["member"])) {',
      "    if (in_array(#{user_id}, $group[\"member\"])) { $user_groups[] = $group[\"name\"]; }",
      '  }',
      '}',
      # Intentionally run set_groups before and after to ensure group membership gets fully applied.
      'local_user_set_groups($user_entry, $user_groups);',
      'local_user_set($user_entry);',
      'local_user_set_groups($user_entry, $user_groups);'
  )
end

#config_parsed?Boolean

Determines if the configuration has been parsed during this session.

Returns:

  • (Boolean)


87
88
89
# File 'lib/shells/pf_shell_wrapper.rb', line 87

def config_parsed?
  config_parsed
end

#enable_cert_auth(public_key = '~/.ssh/id_rsa.pub') ⇒ Object

Enabled public key authentication for the current pfSense user.

Once this has been done you should be able to connect without using a password.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/shells/pf_shell_wrapper.rb', line 155

def enable_cert_auth(public_key = '~/.ssh/id_rsa.pub')
  cert_regex = /^ssh-[rd]sa (?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)? \S*$/m

  # get our cert unless the user provided a full cert for us.
  unless public_key =~ cert_regex
    public_key = File.expand_path(public_key)
    if File.exist?(public_key)
      public_key = File.read(public_key).to_s.strip
    else
      raise Shells::PfSenseCommon::PublicKeyNotFound
    end
    raise Shells::PfSenseCommon::PublicKeyInvalid unless public_key =~ cert_regex
  end

  cfg = get_config_section 'system'
  user_id = nil
  user_name = options[:user].downcase
  cfg['user'].each_with_index do |user,index|
    if user['name'].downcase == user_name
      user_id = index

      authkeys = Base64.decode64(user['authorizedkeys'].to_s).gsub("\r\n", "\n").strip
      unless authkeys == '' || authkeys =~ cert_regex
        warn "Existing authorized keys for user #{options[:user]} are invalid and are being reset."
        authkeys = ''
      end

      if authkeys == ''
        user['authorizedkeys'] = Base64.strict_encode64(public_key)
      else
        authkeys = authkeys.split("\n")
        unless authkeys.include?(public_key)
          authkeys << public_key unless authkeys.include?(public_key)
          user['authorizedkeys'] = Base64.strict_encode64(authkeys.join("\n"))
        end
      end

      break
    end
  end


  raise Shells::PfSenseCommon::UserNotFound unless user_id

  set_config_section 'system', cfg, "Enable certificate authentication for #{options[:user]}."

  apply_user_config user_id
end

#exec(*commands) ⇒ Object

Executes a series of commands on the pfSense shell.



72
73
74
75
76
# File 'lib/shells/pf_shell_wrapper.rb', line 72

def exec(*commands)
  ret = ''
  commands.each { |cmd| ret += shell.exec(cmd) }
  ret + shell.exec('exec')
end

#get_config_section(section_name) ⇒ Object

Gets a configuration section from the pfSense device.



93
94
95
96
# File 'lib/shells/pf_shell_wrapper.rb', line 93

def get_config_section(section_name)
  parse_config unless config_parsed?
  JSON.parse exec("echo json_encode($config[#{section_name.to_s.inspect}]);").strip
end

#parse_configObject

Reloads the pfSense configuration on the device.



80
81
82
83
# File 'lib/shells/pf_shell_wrapper.rb', line 80

def parse_config
  exec 'parse_config(true);'
  self.config_parsed = true
end

#quitObject

Exits the shell session immediately.

Raises:



214
215
216
217
# File 'lib/shells/pf_shell_wrapper.rb', line 214

def quit
  raise Shells::NotRunning unless running?
  raise Shells::ShellBase::QuitNow
end

#rebootObject

Exits the shell session immediately and requests a reboot of the pfSense device.

Raises:



207
208
209
210
# File 'lib/shells/pf_shell_wrapper.rb', line 207

def reboot
  raise Shells::NotRunning unless running?
  raise Shells::PfSenseCommon::RestartNow
end

#set_config_section(section_name, values, message = '') ⇒ Object

Sets a configuration section to the pfSense device.

Returns the number of changes made to the configuration.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/shells/pf_shell_wrapper.rb', line 102

def set_config_section(section_name, values, message = '')
  current_values = get_config_section(section_name)
  changes = generate_config_changes("$config[#{section_name.to_s.inspect}]", current_values, values)
  if changes&.any?
    if message.to_s.strip == ''
      message = "Updating #{section_name} section."
    end
    changes << "write_config(#{message.inspect});"

    exec(*changes)

    (changes.size - 1)
  else
    0
  end
end