puppet-sec-lint 0.1.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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