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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +4 -2
- data/.idea/puppet-sec-lint.iml +7 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +7 -1
- data/README.md +35 -18
- data/_config.yml +1 -0
- data/docs/404.html +24 -0
- data/docs/Gemfile +30 -0
- data/docs/Gemfile.lock +275 -0
- data/docs/_config.yml +41 -0
- data/docs/_posts/2021-05-03-welcome-to-jekyll.markdown +25 -0
- data/docs/_site/404.html +71 -0
- data/docs/_site/feed.xml +13 -0
- data/docs/_site/index.html +1 -0
- data/docs/_site/jekyll/update/2021/05/03/welcome-to-jekyll.html +77 -0
- data/docs/hard-coded-credentials.md +17 -0
- data/docs/index.md +6 -0
- data/exe/puppet-sec-lint +69 -15
- data/lib/configurations/configuration.rb +2 -1
- data/lib/configurations/regex_configuration.rb +9 -0
- data/lib/facades/configuration_file_facade.rb +3 -1
- data/lib/facades/configuration_page_facade.rb +6 -0
- data/lib/lol.pp +6 -6
- data/lib/puppet-sec-lint/version.rb +3 -1
- data/lib/rule_engine.rb +15 -3
- data/lib/rules/admin_by_default_rule.rb +33 -0
- data/lib/rules/cyrillic_homograph_attack.rb +27 -0
- data/lib/rules/empty_password_rule.rb +35 -0
- data/lib/rules/hard_coded_credentials_rule.rb +22 -31
- data/lib/rules/invalid_ip_addr_binding_rule.rb +37 -0
- data/lib/rules/no_http_rule.rb +26 -9
- data/lib/rules/rule.rb +72 -0
- data/lib/rules/suspicious_comment_rule.rb +28 -0
- data/lib/rules/use_weak_crypto_algorithms_rule.rb +28 -0
- data/lib/servers/language_server.rb +100 -0
- data/lib/servers/linter_server.rb +50 -0
- data/lib/{sin.rb → sin/sin.rb} +6 -1
- data/lib/sin/sin_type.rb +44 -0
- data/lib/test.txt +15 -0
- data/lib/test2.rb +16 -0
- data/lib/test3.rb +32 -0
- data/lib/test_new.rb +19 -0
- data/lol2.pp +83 -0
- metadata +30 -5
- data/lib/language_server.rb +0 -78
- data/lib/sin_type.rb +0 -12
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
|
-
|
16
|
-
|
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
|
-
@
|
5
|
-
@
|
6
|
-
@
|
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+=[@
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
data/lib/rules/no_http_rule.rb
CHANGED
@@ -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
|
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.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|