convert_wireguard.rb 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. require 'yaml'
  2. def sanitize_string str
  3. return str.dump[1...-1]
  4. end
  5. def ansible_encrypt to_encrypt
  6. res = `echo -n #{to_encrypt} | ansible-vault encrypt_string --vault-password-file=#{$vault_file}`
  7. if res =~ /(!vault \|[A-Za-z_;.0-9\n\s$]*)$/
  8. return $1
  9. else
  10. raise 'Failed to encrypt'
  11. end
  12. end
  13. if ARGV[0]
  14. $vault_file = ARGV[0].dup.strip.freeze
  15. else
  16. raise "No vault file given"
  17. end
  18. puts "Vault file is #{sanitize_string $vault_file}."
  19. module WGConfParser
  20. class ParserError < RuntimeError; end;
  21. # Not suitable for multi-threading!!!
  22. # Not suitable for wireguard configurations with more than one peer
  23. def self.load cnt
  24. current_section = nil
  25. comment = ''
  26. res = {}
  27. cnt.each_line do |line|
  28. line.strip!
  29. if line =~ /^\[(\S+)\]$/
  30. raise ParserError, "Section #{current_section} doubled" if res.has_key? $1
  31. current_section = $1
  32. res[current_section] = {}
  33. elsif line =~ /^#.*$/
  34. comment += "#{$1}\n"
  35. elsif line =~ /^(.+?)\s*=\s*(.+?)$/
  36. raise ParserError, 'Key-Value-Pair outside a section' if current_section == nil
  37. if ! res[current_section][$1]
  38. res[current_section][$1] = []
  39. end
  40. res[current_section][$1] << $2
  41. elsif line.empty?
  42. # ignore line
  43. else
  44. puts "Unknown statement: #{line}"
  45. end
  46. end
  47. return res, comment
  48. end
  49. end
  50. configurations = []
  51. Dir['wireguard/*.conf'].each do |file|
  52. begin
  53. puts "Parsing #{sanitize_string file}"
  54. configurations << [file, WGConfParser.load(File.read file)]
  55. rescue WGConfParser::ParserError => e
  56. puts "Failed to parse file #{sanitize_string file}: #{sanitize_string e.message}"
  57. exit!
  58. end
  59. end
  60. new_configuration = []
  61. autostart_wg = File.read 'systemd'
  62. configurations.each do |conf_pair|
  63. file = conf_pair[0]
  64. conf = conf_pair[1][0]
  65. comment = conf_pair[1][1].strip
  66. puts "Processing #{sanitize_string file}"
  67. raise 'Configuration has no Interface section' if ! conf.has_key? 'Interface'
  68. raise 'Configuration has no Peer section' if ! conf.has_key? 'Peer'
  69. raise 'Configuration has more than two sections' if conf.keys.length > 2
  70. raise 'Unknown key in Interface section' if ! (conf['Interface'].keys - ['ListenPort', 'PrivateKey', 'PostUp', 'Table', 'Address']).empty?
  71. raise 'Unknown key in Peer section' if ! (conf['Peer'].keys - ['Endpoint', 'PublicKey', 'PresharedKey', 'AllowedIPs', 'PersistentKeepalive']).empty?
  72. res = {}
  73. res['name'] = File.basename(file).delete_suffix('.conf')
  74. res['autostart'] = autostart_wg.include? res['name']
  75. res["listen_port"] = conf['Interface']['ListenPort'][0].to_i
  76. raise 'Unknown listen port' if conf['Interface']['ListenPort'][0] != res["listen_port"].to_s
  77. res["privkey"] = ansible_encrypt conf['Interface']['PrivateKey'][0]
  78. res['addresses'] = []
  79. if conf['Interface']['PostUp']
  80. conf['Interface']['PostUp'].each do |postup|
  81. if postup =~ /^(?:\/sbin\/)?ip addr add dev %i ([a-f0-9.:\/]+) peer ([a-f0-9.:\/]+)$/
  82. inner_res = {}
  83. inner_res['local'] = $1
  84. inner_res['remote'] = $2
  85. res['addresses'] << inner_res
  86. else
  87. raise "Unknown PostUp statment"
  88. end
  89. end
  90. end
  91. res["table"] = true
  92. if conf['Interface']['Table']
  93. case conf['Interface']['Table'][0]
  94. when 'on'
  95. res["table"] = true
  96. when 'off'
  97. res["table"] = false
  98. else
  99. raise 'Unknown table value'
  100. end
  101. end
  102. if conf['Interface']['Address']
  103. conf['Interface']['Address'].each do |addr|
  104. res["addresses"] << {'local' => addr}
  105. end
  106. end
  107. res["peers"] = []
  108. peer = {}
  109. peer['endpoint'] = conf['Peer']['Endpoint'][0] if conf['Peer']['Endpoint']
  110. peer['pubkey'] = conf['Peer']['PublicKey'][0]
  111. peer['psk'] = ansible_encrypt conf['Peer']['PresharedKey'][0] if conf['Peer']['PresharedKey']
  112. peer['allowed_ips'] = conf['Peer']['AllowedIPs'][0].split ','
  113. peer['keepalive'] = conf['Peer']['PersistentKeepalive'][0] if conf['Peer']['PersistentKeepalive']
  114. res["peers"] << peer
  115. res["comment"] = comment if ! comment.empty?
  116. new_configuration << res
  117. end
  118. cnt = YAML.dump({'wireguard' => new_configuration})
  119. # Necessary, because the Ruby YAML module decodes
  120. # the vault string as a "normal" string
  121. cnt.gsub! "|-\n !vault |", '!vault |'
  122. cnt.gsub! "|-\n !vault |", '!vault |'
  123. File.write 'wireguard.yml', cnt