puppet-sec-lint 0.1.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +4 -2
  3. data/.idea/puppet-sec-lint.iml +7 -4
  4. data/Gemfile +3 -1
  5. data/Gemfile.lock +7 -1
  6. data/README.md +35 -18
  7. data/_config.yml +1 -0
  8. data/docs/404.html +24 -0
  9. data/docs/Gemfile +30 -0
  10. data/docs/Gemfile.lock +275 -0
  11. data/docs/_config.yml +41 -0
  12. data/docs/_posts/2021-05-03-welcome-to-jekyll.markdown +25 -0
  13. data/docs/_site/404.html +71 -0
  14. data/docs/_site/feed.xml +13 -0
  15. data/docs/_site/index.html +1 -0
  16. data/docs/_site/jekyll/update/2021/05/03/welcome-to-jekyll.html +77 -0
  17. data/docs/hard-coded-credentials.md +17 -0
  18. data/docs/index.md +6 -0
  19. data/exe/puppet-sec-lint +69 -15
  20. data/lib/configurations/configuration.rb +2 -1
  21. data/lib/configurations/regex_configuration.rb +9 -0
  22. data/lib/facades/configuration_file_facade.rb +3 -1
  23. data/lib/facades/configuration_page_facade.rb +6 -0
  24. data/lib/lol.pp +6 -6
  25. data/lib/puppet-sec-lint/version.rb +3 -1
  26. data/lib/rule_engine.rb +15 -3
  27. data/lib/rules/admin_by_default_rule.rb +33 -0
  28. data/lib/rules/cyrillic_homograph_attack.rb +27 -0
  29. data/lib/rules/empty_password_rule.rb +35 -0
  30. data/lib/rules/hard_coded_credentials_rule.rb +22 -31
  31. data/lib/rules/invalid_ip_addr_binding_rule.rb +37 -0
  32. data/lib/rules/no_http_rule.rb +26 -9
  33. data/lib/rules/rule.rb +72 -0
  34. data/lib/rules/suspicious_comment_rule.rb +28 -0
  35. data/lib/rules/use_weak_crypto_algorithms_rule.rb +28 -0
  36. data/lib/servers/language_server.rb +100 -0
  37. data/lib/servers/linter_server.rb +50 -0
  38. data/lib/{sin.rb → sin/sin.rb} +6 -1
  39. data/lib/sin/sin_type.rb +44 -0
  40. data/lib/test.txt +15 -0
  41. data/lib/test2.rb +16 -0
  42. data/lib/test3.rb +32 -0
  43. data/lib/test_new.rb +19 -0
  44. data/lol2.pp +83 -0
  45. metadata +30 -5
  46. data/lib/language_server.rb +0 -78
  47. data/lib/sin_type.rb +0 -12
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PuppetSecLint
4
- VERSION = "0.1.2"
4
+ VERSION = "0.5.0"
5
+ YEAR = "2021"
6
+ AUTHOR = "Tiago Ribeiro"
5
7
  end
data/lib/rule_engine.rb CHANGED
@@ -2,18 +2,30 @@ require 'puppet-lint'
2
2
  require_relative 'rules/rule'
3
3
  require_relative 'rules/hard_coded_credentials_rule'
4
4
  require_relative 'rules/no_http_rule'
5
+ require_relative 'rules/admin_by_default_rule'
6
+ require_relative 'rules/empty_password_rule'
7
+ require_relative 'rules/invalid_ip_addr_binding_rule'
8
+ require_relative 'rules/suspicious_comment_rule'
9
+ require_relative 'rules/use_weak_crypto_algorithms_rule'
10
+ require_relative 'rules/cyrillic_homograph_attack'
5
11
 
6
12
 
7
13
  class RuleEngine
8
- @rules=[HardCodedCredentialsRule,NoHTTPRule]
14
+ @rules=[HardCodedCredentialsRule,NoHTTPRule,AdminByDefaultRule,EmptyPasswordRule,InvalidIPAddrBindingRule,UseWeakCryptoAlgorithmsRule,SuspiciousCommentRule,CyrillicHomographAttack]
9
15
 
10
16
  class << self
11
17
  attr_accessor :rules
12
18
  end
13
19
 
14
20
  def self.getTokens(code)
15
- lexer = PuppetLint::Lexer.new
16
- tokens = lexer.tokenise(code)
21
+ begin
22
+ lexer = PuppetLint::Lexer.new
23
+ tokens = lexer.tokenise(code)
24
+ rescue
25
+ puts "Error in getting tokens from Puppet-Lint"
26
+ tokens = []
27
+ end
28
+
17
29
  return tokens
18
30
  end
19
31
 
@@ -0,0 +1,33 @@
1
+ require_relative '../configurations/list_configuration'
2
+
3
+ class AdminByDefaultRule < Rule
4
+ @name = "Admin by default"
5
+
6
+ @credentials = /user|usr|pass(word|_|$)|pwd/
7
+
8
+ @credentials_conf = RegexConfiguration.new("Regular expression of words present in credentials", @credentials, "Regular expression of words that if present indicate the existence of a secret.")
9
+
10
+ @configurations+=[@credentials_conf]
11
+
12
+ def self.AnalyzeTokens(tokens)
13
+ result = []
14
+
15
+ ftokens = self.get_tokens(tokens,'admin')
16
+ ftokens.each do |token|
17
+ token_value = token.value.downcase
18
+ token_type = token.type.to_s
19
+ if ["EQUALS", "FARROW"].include? token.prev_code_token.type.to_s
20
+ prev_token = token.prev_code_token
21
+ left_side = prev_token.prev_code_token
22
+ if left_side.value.downcase =~ @credentials_conf.value and ["VARIABLE", "NAME"].include? left_side.type.to_s
23
+ if token_value == 'admin'
24
+ result.append(Sin.new(SinType::AdminByDefault, left_side.line, left_side.column, token.line, token.column+token_value.length))
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ return result
31
+ end
32
+
33
+ end
@@ -0,0 +1,27 @@
1
+ require_relative '../configurations/list_configuration'
2
+
3
+ class CyrillicHomographAttack < Rule
4
+ @name = "Cyrillic Homograph attack"
5
+
6
+ @site_w_cyrillic = /^(http(s)?:\/\/)?.*\p{Cyrillic}+/
7
+
8
+ @site_w_cyrillic_conf = RegexConfiguration.new("Regular expression of links with Cyrillic characters", @site_w_cyrillic, "Regular expression of website links that have Cyrillic characters.")
9
+
10
+ @configurations+=[@site_w_cyrillic_conf]
11
+
12
+ def self.AnalyzeTokens(tokens)
13
+ result = []
14
+
15
+ ftokens = self.filter_tokens(tokens)
16
+ tokens.each do |token|
17
+ token_value = token.value.downcase
18
+ token_type = token.type.to_s
19
+ if ["STRING", "SSTRING"].include? token_type and token_value =~ @site_w_cyrillic_conf.value
20
+ result.append(Sin.new(SinType::CyrillicHomographAttack, token.line, token.column, token.line, token.column+token_value.length))
21
+ end
22
+ end
23
+
24
+ return result
25
+ end
26
+
27
+ end
@@ -0,0 +1,35 @@
1
+ require_relative '../configurations/list_configuration'
2
+
3
+ class EmptyPasswordRule < Rule
4
+ @default_trigger_words = %w[pwd password pass]
5
+ @password = /pass(word|_|$)|pwd/
6
+
7
+ @trigger_words_conf = ListConfiguration.new("List of trigger words", @default_trigger_words, "List of words that identify a password variable")
8
+ @password_conf = RegexConfiguration.new("Regular expression of password name", @password, "Regular expression of names used for password variables.")
9
+
10
+ @configurations+=[@trigger_words_conf, @password_conf]
11
+
12
+ @name = "Check empty password"
13
+
14
+ def self.AnalyzeTokens(tokens)
15
+ result = []
16
+
17
+ ftokens = self.get_string_tokens(tokens,'')
18
+ ftokens.each do |token|
19
+ token_value = token.value.downcase
20
+ token_type = token.type.to_s
21
+ if ["EQUALS", "FARROW"].include? token.prev_code_token.type.to_s
22
+ prev_token = token.prev_code_token
23
+ left_side = prev_token.prev_code_token
24
+ if left_side.value.downcase =~ @password_conf.value and ["VARIABLE", "NAME"].include? left_side.type.to_s
25
+ if token_value == ''
26
+ result.append(Sin.new(SinType::EmptyPassword, prev_token.line, prev_token.column, token.line, token.column+token_value.length))
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ return result
33
+ end
34
+
35
+ end
@@ -1,40 +1,34 @@
1
1
  require_relative '../configurations/list_configuration'
2
+ require_relative '../configurations/regex_configuration'
2
3
 
3
4
  class HardCodedCredentialsRule < Rule
4
- @default_trigger_words = %w[pwd password pass uuid key crypt secret certificate id cert token ssh_key md5 rsa ssl]
5
- @trigger_words_conf = ListConfiguration.new("List of trigger words", @default_trigger_words, "List of words that identify a variable with credentials")
6
- @test_conf = BooleanConfiguration.new("Test configuration", true, "This is just a test configuration")
5
+ @not_considered_creds = %w[pe-puppet pe-webserver pe-puppetdb pe-postgres pe-console-services pe-orchestration-services pe-ace-server pe-bolt-server]
6
+ @invalid_values = %w[undefined unset www-data wwwrun www no yes [] root]
7
+ @secrets = /user|usr|pass(word|_|$)|pwd|key|secret/
8
+ @non_secrets = /gpg|path|type|buff|zone|mode|tag|header|scheme|length|guid/
9
+
10
+ @not_considered_creds_conf = ListConfiguration.new("List of known words not considered in credentials", @not_considered_creds, "List of words not considered secrets by the community (https://puppet.com/docs/pe/2019.8/what_gets_installed_and_where.html#user_and_group_accounts_installed)")
11
+ @invalid_values_conf = ListConfiguration.new("List of invalid values in credentials", @invalid_values, "List of words that are not valid in a credential, advised by puppet specialists.")
12
+ @secrets_conf = RegexConfiguration.new("Regular expression of words present in credentials", @secrets, "Regular expression of words that if present indicate the existence of a secret.")
13
+ @non_secrets_conf = RegexConfiguration.new("Regular expression of words not present in credentials", @non_secrets, "Regular expression of words that if present discard the existence of a secret.")
7
14
 
8
15
  @name = "Hard Coded Credentials"
9
- @configurations+=[@trigger_words_conf,@test_conf]
16
+ @configurations+=[@not_considered_creds_conf, @invalid_values_conf, @secrets_conf, @non_secrets_conf]
10
17
 
11
18
  def self.AnalyzeTokens(tokens)
12
19
  result = []
13
20
 
14
- tokens.each do |indi_token|
15
- nxt_token = indi_token.next_code_token # next token which is not a white space
16
- if (!nxt_token.nil?) && (!indi_token.nil?)
17
- token_type = indi_token.type.to_s ### this gives type for current token
18
-
19
- token_line = indi_token.line ### this gives type for current token
20
- nxt_tok_line = nxt_token.line
21
-
22
- nxt_nxt_token = nxt_token.next_code_token # get the next next token to get key value pair
23
-
24
- if (!nxt_nxt_token.nil?)
25
- nxt_nxt_line = nxt_nxt_token.line
26
- if (token_type.eql? 'NAME') || (token_type.eql? 'VARIABLE')
27
- # puts "Token type: #{token_type}"
28
- if (token_line==nxt_nxt_line)
29
- token_valu = indi_token.value.downcase
30
- nxt_nxt_val = nxt_nxt_token.value.downcase
31
- nxt_nxt_type = nxt_nxt_token.type.to_s ## to handle false positives,
32
-
33
- if (self.TriggerWordInString(token_valu)) && ((nxt_nxt_val.length > 0)) && ((!nxt_nxt_type.eql? 'VARIABLE') && (!token_valu.include? "("))
34
- result.append(Sin.new(SinType::HardCodedCred, indi_token.line, indi_token.column, nxt_nxt_token.line, nxt_nxt_token.column+nxt_nxt_token.value.length))
35
- end
36
- end
37
- end
21
+ ftokens = self.filter_tokens(tokens)
22
+ ftokens.each do |token|
23
+ token_value = token.value.downcase
24
+ token_type = token.type.to_s
25
+ next_token = token.next_code_token
26
+ # accepts <VARIABLE> <EQUALS> secret OR <NAME> <FARROW> secret, checks if <VARIABLE> | <NAME> satisfy SECRETS but not satisfy NON_SECRETS
27
+ if ["VARIABLE", "NAME"].include? token_type and ["EQUALS", "FARROW"].include? next_token.type.to_s and token_value =~ @secrets_conf.value and !(token_value =~ @non_secrets_conf.value)
28
+ right_side_type = next_token.next_code_token.type.to_s
29
+ right_side_value = next_token.next_code_token.value.downcase
30
+ if ["STRING", "SSTRING"].include? right_side_type and right_side_value.length > 1 and !@invalid_values_conf.value.include? right_side_value and !(right_side_value =~ /::|\/|\.|\\/ ) and !@not_considered_creds_conf.value.include? right_side_value
31
+ result.append(Sin.new(SinType::HardCodedCred, token.line, token.column, next_token.next_code_token.line, next_token.next_code_token.column+right_side_value.length))
38
32
  end
39
33
  end
40
34
  end
@@ -42,7 +36,4 @@ class HardCodedCredentialsRule < Rule
42
36
  return result
43
37
  end
44
38
 
45
- def self.TriggerWordInString(string)
46
- return @trigger_words_conf.value.any? { |word| string.include?(word) }
47
- end
48
39
  end
@@ -0,0 +1,37 @@
1
+ require_relative '../configurations/list_configuration'
2
+
3
+ class InvalidIPAddrBindingRule < Rule
4
+ @name = "Invalid IP Address Binding"
5
+
6
+ @ip_addr_bin_regex = /^((http(s)?:\/\/)?0.0.0.0(:\d{1,5})?)$/
7
+
8
+ @ip_addr_bin_regex_conf = RegexConfiguration.new("Regular expression of an invalid IP address", @ip_addr_bin_regex, "Regular expression of an IP address considered invalid or insecure to use.")
9
+
10
+ @configurations+=[@ip_addr_bin_regex_conf]
11
+
12
+ def self.AnalyzeTokens(tokens)
13
+ result = []
14
+
15
+ ftokens = get_tokens(tokens,"0.0.0.0")
16
+ ftokens.each do |token|
17
+ token_value = token.value.downcase
18
+ token_type = token.type.to_s
19
+ if ["EQUALS", "FARROW"].include? token.prev_code_token.type.to_s
20
+ prev_token = token.prev_code_token
21
+ left_side = prev_token.prev_code_token
22
+ if token_value =~ @ip_addr_bin_regex_conf.value and ["VARIABLE", "NAME"].include? left_side.type.to_s
23
+ result.append(Sin.new(SinType::InvalidIPAddrBinding, left_side.line, left_side.column, token.line, token.column+token_value.length))
24
+ end
25
+ end
26
+ end
27
+
28
+ return result
29
+ end
30
+
31
+ def self.filter_tokens_per_value(tokens, token)
32
+ ftokens=tokens.find_all do |hash|
33
+ (hash.type.to_s == 'SSTRING' || hash.type.to_s == 'STRING') and hash.value.downcase.include? token
34
+ end
35
+ return ftokens
36
+ end
37
+ end
@@ -1,19 +1,36 @@
1
1
  require_relative '../configurations/list_configuration'
2
- require_relative '../sin'
3
- require_relative '../sin_type'
2
+ require_relative '../sin/sin'
3
+ require_relative '../sin/sin_type'
4
4
 
5
5
  class NoHTTPRule < Rule
6
- @name="No HTTP Connections"
6
+ @name="No HTTPS Connections"
7
+
8
+ @resources = %w[apt::source ::apt::source wget::fetch yumrepo yum:: aptly::mirror util::system_package yum::managed_yumrepo]
9
+ @keywords = %w[backport key download uri mirror]
10
+ @http = /^http:\/\/.+/
11
+ @whitelist = [] # Todo:Need to check how is this set up
12
+
13
+ @resources_conf = ListConfiguration.new("List of resources that can use HTTP", @resources, "List of resources that are known to not use HTTPS but that validate the transferred content with other secure methods.")
14
+ @keywords_conf = ListConfiguration.new("List of keywords for URLs", @keywords, "List of keywords that identify hyperlinks that should be analyzed.")
15
+ @http_conf = RegexConfiguration.new("Regular expression of a normal HTTP address", @http, "Regular expression that identifies the URL of a website using the regular non-secure HTTP protocol.")
16
+
17
+ @configurations+=[@resources_conf, @keywords_conf, @http_conf]
7
18
 
8
19
  def self.AnalyzeTokens(tokens)
9
20
  result = []
10
21
 
11
- tokens.each do |indi_token|
12
- token_valu = indi_token.value ### this gives each token
13
- token_valu = token_valu.downcase
14
- token_type = indi_token.type.to_s
15
- if (token_valu.include? "http://" ) && (!token_type.eql? "COMMENT")
16
- result.append(Sin.new(SinType::HttpWithoutTLS, indi_token.line, indi_token.column, indi_token.line, indi_token.column+indi_token.value.length))
22
+ ptokens = self.filter_resources(tokens, @resources_conf.value)
23
+ ctokens = self.filter_variables(ptokens, @keywords_conf.value)
24
+ if @whitelist
25
+ wtokens = self.filter_whitelist(ctokens)
26
+ else
27
+ wtokens = ptokens
28
+ end
29
+ wtokens.each do |token|
30
+ token_value = token.value.downcase
31
+ token_type = token.type.to_s
32
+ if (token_value =~ @http_conf.value)
33
+ result.append(Sin.new(SinType::HttpWithoutTLS, token.line, token.column, token.line, token.column+token_value.length))
17
34
  end
18
35
  end
19
36
 
data/lib/rules/rule.rb CHANGED
@@ -14,4 +14,76 @@ class Rule
14
14
  puts "Implement this"
15
15
  return
16
16
  end
17
+
18
+ def self.get_tokens(tokens, token)
19
+ ftokens=tokens.find_all do |hash|
20
+ (hash.type.to_s == 'NAME' || hash.type.to_s == 'VARIABLE' || hash.type.to_s == 'SSTRING' || hash.type.to_s == 'STRING') and hash.value.downcase.include? token
21
+ end
22
+ return ftokens
23
+ end
24
+
25
+ def self.filter_tokens(tokens)
26
+ ftokens=tokens.find_all do |hash|
27
+ (hash.type.to_s == 'SSTRING' || hash.type.to_s == 'STRING' || hash.type.to_s == 'VARIABLE' || hash.type.to_s == 'NAME')
28
+ end
29
+ return ftokens
30
+ end
31
+
32
+ def self.filter_resources(tokens, resources)
33
+ is_resource = false
34
+ brackets = 0
35
+ ftokens=tokens.find_all do |hash|
36
+
37
+ if resources.include? hash.value.downcase
38
+ is_resource = true
39
+ elsif is_resource and hash.type.to_s == "LBRACE"
40
+ brackets += 1
41
+ elsif is_resource and hash.type.to_s == "RBRACE"
42
+ brackets -=1
43
+ end
44
+
45
+ if is_resource and hash.type.to_s == "RBRACE" and brackets == 0
46
+ is_resource = false
47
+ end
48
+
49
+ if !is_resource
50
+ (hash.type.to_s == 'NAME' || hash.type.to_s == 'VARIABLE' || hash.type.to_s == 'SSTRING' || hash.type.to_s == 'STRING')
51
+ end
52
+ end
53
+ return ftokens
54
+ end
55
+
56
+ def self.get_string_tokens(tokens, token)
57
+ ftokens=tokens.find_all do |hash|
58
+ (hash.type.to_s == 'SSTRING' || hash.type.to_s == 'STRING') and hash.value.downcase.include? token
59
+ end
60
+ return ftokens
61
+ end
62
+
63
+ def self.get_comments(tokens)
64
+ ftokens=tokens.find_all do |hash|
65
+ (hash.type.to_s == 'COMMENT' || hash.type.to_s == 'MLCOMMENT' || hash.type.to_s == 'SLASH_COMMENT')
66
+ end
67
+ return ftokens
68
+ end
69
+
70
+ def self.filter_whitelist(tokens)
71
+ ftokens=tokens.find_all do |hash|
72
+ #!(@whitelist =~ hash.value.downcase)
73
+ true # TODO: Understand the whitelist
74
+ end
75
+ return ftokens
76
+ end
77
+
78
+ def self.filter_variables(tokens, keywords)
79
+ line = -1
80
+ kw_regex = Regexp.new keywords.join("|")
81
+ ftokens=tokens.find_all do |hash|
82
+ if (hash.type.to_s == 'VARIABLE' || hash.type.to_s == 'NAME') and hash.value.downcase =~ kw_regex
83
+ line = hash.line
84
+ elsif hash.line != line
85
+ hash
86
+ end
87
+ end
88
+ end
17
89
  end
@@ -0,0 +1,28 @@
1
+ require_relative '../configurations/list_configuration'
2
+
3
+ class SuspiciousCommentRule < Rule
4
+ @trigger_words = %w[hack fixme later later2 todo ticket launchpad bug to-do]
5
+ @suspicious = /hack|fixme|ticket|bug|secur|debug|defect|weak/
6
+
7
+ @trigger_words_conf = ListConfiguration.new("List of trigger words", @trigger_words, "List of words that identify a suspicious comment")
8
+ @suspicious_conf = RegexConfiguration.new("Regular expression of keywords present in suspicious comments", @suspicious, "Regular expression that identifies words that are immediately considered suspicious comments that shouldn't be present in a finalized product.")
9
+
10
+ @configurations+=[@trigger_words_conf, @suspicious_conf]
11
+
12
+ @name = "Suspicious comments"
13
+
14
+ def self.AnalyzeTokens(tokens)
15
+ result = []
16
+
17
+ ftokens = self.get_comments(tokens)
18
+ ftokens.each do |token|
19
+ token_value = token.value.downcase
20
+ token_type = token.type.to_s
21
+ if (token_value =~ @suspicious_conf.value)
22
+ result.append(Sin.new(SinType::SuspiciousComments, token.line, token.column, token.line, token.column+token_value.length))
23
+ end
24
+ end
25
+
26
+ return result
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../configurations/list_configuration'
2
+
3
+ class UseWeakCryptoAlgorithmsRule < Rule
4
+ @name = "Use of weak crypto algorithm"
5
+
6
+ @poor_crypto = /^(sha1|md5)/
7
+
8
+ @poor_crypto_conf = RegexConfiguration.new("Regular expression of weak Crypto Algorithms", @poor_crypto, "Regular expression for names of known weak Cryptographic algorithms that shouldn't be used to secure sensitive information.")
9
+
10
+ @configurations+=[@poor_crypto_conf]
11
+
12
+ def self.AnalyzeTokens(tokens)
13
+ result = []
14
+
15
+ tokens.each do |token|
16
+ token_value = token.value.downcase
17
+ token_type = token.type.to_s
18
+ if !token.next_token.nil?
19
+ next_token_type = token.next_token.type.to_s
20
+ end
21
+ if (token_value =~ @poor_crypto_conf.value) && (next_token_type.eql? "LPAREN")
22
+ result.append(Sin.new(SinType::WeakCryptoAlgorithm, token.line, token.column, token.line, token.column+token_value.length))
23
+ end
24
+ end
25
+
26
+ return result
27
+ end
28
+ end
@@ -0,0 +1,100 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'socket'
4
+ require_relative '../rule_engine'
5
+ require_relative '../visitors/configuration_visitor'
6
+ require_relative '../facades/configuration_page_facade'
7
+ require_relative '../facades/configuration_file_facade'
8
+
9
+ class LanguageServer
10
+ ConfigurationVisitor.GenerateIDs
11
+ ConfigurationFileFacade.LoadConfigurations
12
+
13
+ def self.start
14
+ server = TCPServer.open(5007)
15
+
16
+ loop {
17
+ Thread.fork(server.accept) do |client|
18
+ while line=client.gets
19
+ length=Integer(line.scan(/\d/).join(''))
20
+ line=client.read(length+2)
21
+ request = JSON.parse(line)
22
+ puts line
23
+
24
+ method_name = request['method'].sub('/', '_')
25
+ response = if self.respond_to? "client_"+method_name then self.send("client_"+method_name,request['id'],request['params']) end
26
+
27
+ if not response.nil?
28
+ client.flush
29
+ client.print("Content-Length: "+response.length.to_s+"\r\n\r\n")
30
+ client.print(response)
31
+ puts response
32
+ end
33
+ end
34
+ client.close
35
+ end
36
+ }
37
+ end
38
+
39
+ def self.client_initialize(id,params)
40
+ return JSON.generate({
41
+ jsonrpc: '2.0',
42
+ result: {
43
+ capabilities: {
44
+ textDocumentSync:1,
45
+ implementationProvider: "true"
46
+ }
47
+ },
48
+ id: id
49
+ })
50
+ end
51
+
52
+ def self.client_textDocument_didOpen(id,params)
53
+ uri = params["textDocument"]["uri"]
54
+ version = params["textDocument"]["version"]
55
+ code = params['textDocument']['text']
56
+ return self.generate_diagnostics(uri,version,code)
57
+ return
58
+ end
59
+
60
+ def self.client_textDocument_didChange(id,params)
61
+ uri = params["textDocument"]["uri"]
62
+ version = params["textDocument"]["version"]
63
+ code = params['contentChanges'][0]['text']
64
+ return self.generate_diagnostics(uri,version,code)
65
+ return
66
+ end
67
+
68
+ def self.generate_diagnostics(uri,version,code)
69
+ result = RuleEngine.analyzeDocument(code) #convert to json
70
+
71
+ diagnostics = []
72
+
73
+ result.each do |sin|
74
+ diagnostics.append({
75
+ range:{
76
+ start: { line: sin.begin_line-1, character: sin.begin_char },
77
+ end: { line: sin.end_line-1, character: sin.end_char }
78
+ },
79
+ severity: 2,
80
+ code: {
81
+ value:sin.type[:name],
82
+ target:sin.type[:solution]
83
+ },
84
+ source:'Puppet-sec-lint',
85
+ message: sin.type[:message]
86
+ })
87
+ end
88
+
89
+ return JSON.generate({
90
+ jsonrpc: '2.0',
91
+ method: 'textDocument/publishDiagnostics',
92
+ params: {
93
+ uri: uri,
94
+ version: version,
95
+ diagnostics: diagnostics
96
+ }
97
+ })
98
+ end
99
+
100
+ end