Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- docker: set LANG=C.UTF-8. Fixes #3690 (@ytti)

### Fixed
- ingate: redact secrets (private keys, passwords, secrets, passphrases, pre-shared keys, tokens and the SNMP community) when remove_secret is set (@thanegill)
- siklu: allow parenthesis in prompt. Fixes #3841 (@ytti)
- fortios: allow parenthesis in prompt. Fixes #3846 (@ytti)
- fortigate: prompt can contain HA cluster status. Fixes #3846 (@robertcheramy)
Expand Down
16 changes: 15 additions & 1 deletion lib/oxidized/model/ingate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,21 @@ class Ingate < Oxidized::Model
end

cmd cfg_cb do |cfg|
cfg.gsub! /^# Timestamp:.*$/, ''
cfg.gsub(/^# Timestamp:.*$/, '')
end

cmd :secret do |cfg|
# Private keys: any *key field whose quoted value is a PEM private key block
# (the value spans multiple lines).
cfg.gsub!(/\b([a-z_]*key)="-----BEGIN [A-Z ]*PRIVATE KEY-----.*?-----END [A-Z ]*PRIVATE KEY-----"/m,
'\1="<secret hidden>"')
# Ingate spreads credentials across many fields. Match by suffix so we cover
# them all (e.g. trunkuserpassword, snmppassword, radiussecret,
# configencpassphrase, xauth_psk, eabhmackey, api_token) without touching
# lookalikes such as passwordtimeout or enable_psk_rw.
cfg.gsub!(/\b([a-z_]*(?:password|secret|passphrase|psk|hmackey|api_token|authtoken))=(?:"[^"]*"|\S+)/,
'\1=<secret hidden>')
cfg.gsub!(/\bcommunity=(?:"[^"]*"|\S+)/, 'community=<secret hidden>')
cfg
end

Expand Down
81 changes: 81 additions & 0 deletions spec/model/ingate_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# frozen_string_literal: true

require_relative 'model_helper'
require 'oxidized/model/ingate'

describe 'Model Ingate' do
before(:each) do
init_model_helper

@mock_node = mock('Oxidized::Node')
@mock_node.stubs(:name).returns('ingate-1')
@mock_node.stubs(:ip).returns('192.0.2.10')
@mock_node.stubs(:group).returns('default')

@mock_input = mock('Oxidized::Input')
@mock_input.stubs(:output).returns(nil)

# The config is downloaded over HTTP (a "config to CLI file"). The values
# below are fabricated; the format mirrors a real SIParator export. The
# command is an anonymous lambda, so the stub matches any argument.
@config = <<~CONFIG
# Unitname: example-sbc
# Product: Software SIParator/Firewall
# Version: 6.4.4
# Timestamp: 2024-01-01 00:00:00

load-factory --all
modify-row snmp.snmp 1 community=public snmppassword=SNMPPW
modify-row misc.settings 1 passwordtimeout=300 enable_psk_rw=on
add-row qturn.default_password {id 1} password=s3cr3tpw
add-row sip.trunks {id 1} trunkuserpassword=TRUNKPW radiussecret=RADSEC
add-row vpn.ipsec {id 1} xauth_psk=XAUTHPSK configencpassphrase=ENCPASS
add-row cwmp.acs {id 1} api_token=APITOKEN123
add-row cert.acme_accounts {id 1} eabhmackey=FAKEHMACKEY1234567890 key="-----BEGIN PRIVATE KEY-----
FAKEACMEKEYLINE
-----END PRIVATE KEY-----"
add-row cert.cert {id 1} cert_key="-----BEGIN PRIVATE KEY-----
FAKECERTKEYLINE
-----END PRIVATE KEY-----"
CONFIG
@mock_input.stubs(:cmd).returns(@config)

@model = Ingate.new
@model.input = @mock_input
@model.node = @mock_node
end

it 'removes the volatile Timestamp line and keeps the configuration' do
@model.stubs(:vars).returns(nil)

result = @model.get.to_cfg

_(result).wont_match(/^# Timestamp:/)
_(result).must_match(/^load-factory --all$/)
_(result).must_match(/^add-row qturn\.default_password /)
end

it 'redacts secrets when remove_secret is set' do
@model.stubs(:vars).returns(nil)
@model.stubs(:vars).with(:remove_secret).returns(true)

result = @model.get.to_cfg

# secret values are gone (across the various field families)
%w[s3cr3tpw SNMPPW TRUNKPW RADSEC XAUTHPSK ENCPASS APITOKEN123
FAKEHMACKEY1234567890 FAKEACMEKEYLINE FAKECERTKEYLINE].each do |secret|
_(result).wont_include(secret)
end
_(result).wont_include('community=public')
# replaced with placeholders, field name kept
%w[password snmppassword trunkuserpassword radiussecret configencpassphrase
xauth_psk api_token eabhmackey community].each do |field|
_(result).must_match(/\b#{field}=<secret hidden>/)
end
_(result).must_match(/\bkey="<secret hidden>"/)
_(result).must_match(/cert_key="<secret hidden>"/)
# lookalikes are NOT redacted
_(result).must_match(/passwordtimeout=300/)
_(result).must_match(/enable_psk_rw=on/)
end
end