gitlab-secret_detection 0.4.0 → 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/README.md +2 -1
- data/lib/gitlab/secret_detection/core/response.rb +7 -3
- data/lib/gitlab/secret_detection/core/status.rb +1 -0
- data/lib/gitlab/secret_detection/grpc/client/grpc_client.rb +131 -7
- data/lib/gitlab/secret_detection/grpc/client/stream_request_enumerator.rb +24 -0
- data/lib/gitlab/secret_detection/grpc/generated/secret_detection_pb.rb +1 -1
- data/lib/gitlab/secret_detection/grpc/scanner_service.rb +13 -8
- data/lib/gitlab/secret_detection/grpc.rb +1 -0
- data/lib/gitlab/secret_detection/utils/certificate.rb +108 -0
- data/lib/gitlab/secret_detection/utils/memoize.rb +151 -0
- data/lib/gitlab/secret_detection/utils.rb +11 -0
- data/lib/gitlab/secret_detection.rb +1 -0
- data/proto/secret_detection.proto +7 -8
- metadata +20 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7ff1ed2f6fa1d52463144cfacf4a23bd191822d6660e5f4d3c15cefb349b9dd
|
4
|
+
data.tar.gz: 13af55c3efd41733108968de3d0c8d03648c726947170636a293db174521a81e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6698802604dffeff97940812c8463dbb21c73698c8f438e825b482d5dc68c97953aaa983d03af7de26f37ff9ec7d1e1418966e495c2ef8f308eb05f40d4ae601
|
7
|
+
data.tar.gz: b6e1b308dddfa644500184058228faf67458702c82994fc9c475ce03ef47a51323f309c1163287a2d1ce0bbd13aaec2a19c71e4a8ca711f6a6beaefbda2649d1
|
data/README.md
CHANGED
@@ -42,6 +42,7 @@ the approach:
|
|
42
42
|
│ ├── core/.. # Secret detection logic (most of it pulled from existing gem)
|
43
43
|
│ └── grpc
|
44
44
|
│ ├── generated/.. # gRPC generated files and secret detection gRPC service
|
45
|
+
│ ├── client/.. # gRPC client to invoke secret detection service's RPC endpoints
|
45
46
|
│ └── scanner_service.rb # Secret detection gRPC service implementation
|
46
47
|
├── examples
|
47
48
|
│ └── sample-client/.. # Sample Ruby RPC client that connects with gRPC server and calls RPC scan
|
@@ -320,7 +321,7 @@ Run `ruby examples/sample-client/sample_client.rb` on your terminal to run the s
|
|
320
321
|
|
321
322
|
## Benchmark
|
322
323
|
|
323
|
-
RPC service is benchmarked using [`ghz`](https://ghz.sh), a powerful CLI-based tool for load testing and benchmarking gRPC services. More details added [here](
|
324
|
+
RPC service is benchmarked using [`ghz`](https://ghz.sh), a powerful CLI-based tool for load testing and benchmarking gRPC services. More details added [here](https://gitlab.com/gitlab-org/gitlab/-/work_items/468107).
|
324
325
|
|
325
326
|
## Project Status
|
326
327
|
|
@@ -7,12 +7,15 @@ module GitLab
|
|
7
7
|
#
|
8
8
|
# +status+:: One of values from GitLab::SecretDetection::Core::Status indicating the scan operation's status
|
9
9
|
# +results+:: Array of GitLab::SecretDetection::Core::Finding values. Default value is nil.
|
10
|
+
# +metadata+:: Hash object containing additional meta information about the response. It is currently used
|
11
|
+
# to embed more information on error.
|
10
12
|
class Response
|
11
|
-
attr_reader :status, :results
|
13
|
+
attr_reader :status, :results, :metadata
|
12
14
|
|
13
|
-
def initialize(status, results = [])
|
15
|
+
def initialize(status, results = [], metadata = {})
|
14
16
|
@status = status
|
15
17
|
@results = results
|
18
|
+
@metadata = metadata
|
16
19
|
end
|
17
20
|
|
18
21
|
def ==(other)
|
@@ -22,6 +25,7 @@ module GitLab
|
|
22
25
|
def to_h
|
23
26
|
{
|
24
27
|
status:,
|
28
|
+
metadata:,
|
25
29
|
results: results&.map(&:to_h)
|
26
30
|
}
|
27
31
|
end
|
@@ -29,7 +33,7 @@ module GitLab
|
|
29
33
|
protected
|
30
34
|
|
31
35
|
def state
|
32
|
-
[status, results]
|
36
|
+
[status, metadata, results]
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -12,6 +12,7 @@ module GitLab
|
|
12
12
|
SCAN_ERROR = 5 # When the scan operation fails due to regex error
|
13
13
|
INPUT_ERROR = 6 # When the scan operation fails due to invalid input
|
14
14
|
NOT_FOUND = 7 # When scan operation completes with zero findings
|
15
|
+
AUTH_ERROR = 8 # When authentication fails
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -1,19 +1,143 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require_relative '
|
3
|
+
require 'grpc'
|
4
|
+
require_relative '../../grpc/scanner_service'
|
5
|
+
require_relative '../../core/response'
|
6
|
+
require_relative '../../core/status'
|
7
|
+
require_relative '../../utils'
|
8
|
+
require_relative './stream_request_enumerator'
|
5
9
|
|
6
10
|
module GitLab
|
7
11
|
module SecretDetection
|
8
12
|
module GRPC
|
9
13
|
class Client
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
include SecretDetection::Utils::StrongMemoize
|
15
|
+
include SDLogger
|
16
|
+
|
17
|
+
# Time to wait for the response from the service
|
18
|
+
REQUEST_TIMEOUT_SECONDS = 10 # 10 seconds
|
19
|
+
|
20
|
+
def initialize(host, secure: false, compression: true)
|
21
|
+
@host = host
|
22
|
+
@secure = secure
|
23
|
+
@compression = compression
|
24
|
+
end
|
25
|
+
|
26
|
+
# Triggers Secret Detection service's `/Scan` gRPC endpoint. To keep it consistent with SDS gem interface,
|
27
|
+
# this method transforms the gRPC response to +GitLab::SecretDetection::Core::Response+.
|
28
|
+
# Furthermore, any errors that are raised by the service will be translated to
|
29
|
+
# +GitLab::SecretDetection::Core::Response+ type by assiging a appropriate +status+ value to it.
|
30
|
+
def run_scan(request:, auth_token:, extra_headers: {})
|
31
|
+
with_rescued_errors do
|
32
|
+
grpc_response = stub.scan(
|
33
|
+
request,
|
34
|
+
metadata: build_metadata(auth_token, extra_headers),
|
35
|
+
deadline: request_deadline
|
36
|
+
)
|
37
|
+
|
38
|
+
convert_to_core_response(grpc_response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Triggers Secret Detection service's `/ScanStream` gRPC endpoint.
|
43
|
+
#
|
44
|
+
# To keep it consistent with SDS gem interface, this method transforms the gRPC response to
|
45
|
+
# +GitLab::SecretDetection::Core::Response+ type. Furthermore, any errors that are raised by the service will be
|
46
|
+
# translated to +GitLab::SecretDetection::Core::Response+ type by assiging a appropriate +status+ value to it.
|
47
|
+
#
|
48
|
+
# Note: If one of the stream requests result in an error, the stream will end immediately without processing the
|
49
|
+
# remaining requests.
|
50
|
+
def run_scan_stream(requests:, auth_token:, extra_headers: {})
|
51
|
+
request_stream = GitLab::SecretDetection::GRPC::StreamRequestEnumerator.new(requests)
|
52
|
+
results = []
|
53
|
+
with_rescued_errors do
|
54
|
+
stub.scan_stream(
|
55
|
+
request_stream.each_item,
|
56
|
+
metadata: build_metadata(auth_token, extra_headers),
|
57
|
+
deadline: request_deadline
|
58
|
+
).each do |grpc_response|
|
59
|
+
response = convert_to_core_response(grpc_response)
|
60
|
+
if block_given?
|
61
|
+
yield response
|
62
|
+
else
|
63
|
+
results << response
|
64
|
+
end
|
65
|
+
end
|
66
|
+
results
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :secure, :host, :compression
|
73
|
+
|
74
|
+
def stub
|
75
|
+
GitLab::SecretDetection::GRPC::Scanner::Stub.new(
|
76
|
+
host,
|
77
|
+
channel_credentials,
|
78
|
+
channel_args:
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
strong_memoize_attr :stub
|
83
|
+
|
84
|
+
def channel_args
|
85
|
+
default_options = {
|
86
|
+
'grpc.keepalive_permit_without_calls' => 1,
|
87
|
+
'grpc.keepalive_time_ms' => 30000, # 30 seconds
|
88
|
+
'grpc.keepalive_timeout_ms' => 10000 # 10 seconds timeout for keepalive response
|
89
|
+
}
|
90
|
+
|
91
|
+
compression_options = ::GRPC::Core::CompressionOptions
|
92
|
+
.new(default_algorithm: :gzip)
|
93
|
+
.to_channel_arg_hash
|
94
|
+
|
95
|
+
default_options.merge!(compression_options) if compression
|
96
|
+
|
97
|
+
default_options.freeze
|
98
|
+
end
|
99
|
+
|
100
|
+
def channel_credentials
|
101
|
+
return :this_channel_is_insecure unless secure
|
102
|
+
|
103
|
+
certs = GitLab::SecretDetection::Utils::X509::Certificate.ca_certs_bundle
|
104
|
+
|
105
|
+
::GRPC::Core::ChannelCredentials.new(certs)
|
13
106
|
end
|
14
107
|
|
15
|
-
def
|
16
|
-
|
108
|
+
def build_metadata(token, extra_headers = {})
|
109
|
+
{ 'x-sd-auth' => token }.merge!(extra_headers).freeze
|
110
|
+
end
|
111
|
+
|
112
|
+
def request_deadline
|
113
|
+
Time.now + REQUEST_TIMEOUT_SECONDS
|
114
|
+
end
|
115
|
+
|
116
|
+
def with_rescued_errors
|
117
|
+
yield
|
118
|
+
rescue ::GRPC::Unauthenticated
|
119
|
+
SecretDetection::Core::Response.new(SecretDetection::Core::Status::AUTH_ERROR)
|
120
|
+
rescue ::GRPC::InvalidArgument => e
|
121
|
+
SecretDetection::Core::Response.new(
|
122
|
+
SecretDetection::Core::Status::INPUT_ERROR, nil, { message: e.details, **e.metadata }
|
123
|
+
)
|
124
|
+
rescue ::GRPC::Unknown, ::GRPC::BadStatus => e
|
125
|
+
SecretDetection::Core::Response.new(
|
126
|
+
SecretDetection::Core::Status::SCAN_ERROR, nil, { message: e.details }
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def convert_to_core_response(grpc_response)
|
131
|
+
response = grpc_response.to_h
|
132
|
+
|
133
|
+
SecretDetection::Core::Response.new(
|
134
|
+
response[:status],
|
135
|
+
response[:results],
|
136
|
+
response[:metadata]
|
137
|
+
)
|
138
|
+
rescue StandardError => e
|
139
|
+
logger.error("Failed to convert to core response: #{e}")
|
140
|
+
SecretDetection::Core::Response.new(SecretDetection::Core::Status::SCAN_ERROR)
|
17
141
|
end
|
18
142
|
end
|
19
143
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitLab
|
4
|
+
module SecretDetection
|
5
|
+
module GRPC
|
6
|
+
class StreamRequestEnumerator
|
7
|
+
def initialize(requests = [])
|
8
|
+
@requests = requests
|
9
|
+
end
|
10
|
+
|
11
|
+
# yields a request, waiting between 0 and 1 seconds between requests
|
12
|
+
#
|
13
|
+
# @return an Enumerable that yields a request input
|
14
|
+
def each_item
|
15
|
+
return enum_for(:each_item) unless block_given?
|
16
|
+
|
17
|
+
@requests.each do |request|
|
18
|
+
yield request
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -5,7 +5,7 @@
|
|
5
5
|
require 'google/protobuf'
|
6
6
|
|
7
7
|
|
8
|
-
descriptor_data = "\n\x16secret_detection.proto\x12\x17gitlab.secret_detection\"\xfc\x03\n\x0bScanRequest\x12>\n\x08payloads\x18\x01 \x03(\x0b\x32,.gitlab.secret_detection.ScanRequest.Payload\x12\x19\n\x0ctimeout_secs\x18\x02 \x01(\x02H\x00\x88\x01\x01\x12!\n\x14payload_timeout_secs\x18\x03 \x01(\x02H\x01\x88\x01\x01\x12\x42\n\nexclusions\x18\x04 \x03(\x0b\x32..gitlab.secret_detection.ScanRequest.Exclusion\x12\x0c\n\x04tags\x18\x05 \x03(\t\x1a#\n\x07Payload\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\x1a\x66\n\tExclusion\x12J\n\x0e\x65xclusion_type\x18\x01 \x01(\x0e\x32\x32.gitlab.secret_detection.ScanRequest.ExclusionType\x12\r\n\x05value\x18\x02 \x01(\t\"f\n\rExclusionType\x12\x1e\n\x1a\x45XCLUSION_TYPE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x45XCLUSION_TYPE_RULE\x10\x01\x12\x1c\n\x18\x45XCLUSION_TYPE_RAW_VALUE\x10\x02\x42\x0f\n\r_timeout_secsB\x17\n\x15_payload_timeout_secs\"\
|
8
|
+
descriptor_data = "\n\x16secret_detection.proto\x12\x17gitlab.secret_detection\"\xfc\x03\n\x0bScanRequest\x12>\n\x08payloads\x18\x01 \x03(\x0b\x32,.gitlab.secret_detection.ScanRequest.Payload\x12\x19\n\x0ctimeout_secs\x18\x02 \x01(\x02H\x00\x88\x01\x01\x12!\n\x14payload_timeout_secs\x18\x03 \x01(\x02H\x01\x88\x01\x01\x12\x42\n\nexclusions\x18\x04 \x03(\x0b\x32..gitlab.secret_detection.ScanRequest.Exclusion\x12\x0c\n\x04tags\x18\x05 \x03(\t\x1a#\n\x07Payload\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\x1a\x66\n\tExclusion\x12J\n\x0e\x65xclusion_type\x18\x01 \x01(\x0e\x32\x32.gitlab.secret_detection.ScanRequest.ExclusionType\x12\r\n\x05value\x18\x02 \x01(\t\"f\n\rExclusionType\x12\x1e\n\x1a\x45XCLUSION_TYPE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x45XCLUSION_TYPE_RULE\x10\x01\x12\x1c\n\x18\x45XCLUSION_TYPE_RAW_VALUE\x10\x02\x42\x0f\n\r_timeout_secsB\x17\n\x15_payload_timeout_secs\"\xe2\x03\n\x0cScanResponse\x12>\n\x07results\x18\x01 \x03(\x0b\x32-.gitlab.secret_detection.ScanResponse.Finding\x12\x0e\n\x06status\x18\x02 \x01(\x05\x1a\x9d\x01\n\x07\x46inding\x12\x12\n\npayload_id\x18\x01 \x01(\t\x12\x0e\n\x06status\x18\x02 \x01(\x05\x12\x11\n\x04type\x18\x03 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0b\x64\x65scription\x18\x04 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0bline_number\x18\x05 \x01(\x05H\x02\x88\x01\x01\x42\x07\n\x05_typeB\x0e\n\x0c_descriptionB\x0e\n\x0c_line_number\"\xe1\x01\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x10\n\x0cSTATUS_FOUND\x10\x01\x12\x1c\n\x18STATUS_FOUND_WITH_ERRORS\x10\x02\x12\x17\n\x13STATUS_SCAN_TIMEOUT\x10\x03\x12\x1a\n\x16STATUS_PAYLOAD_TIMEOUT\x10\x04\x12\x15\n\x11STATUS_SCAN_ERROR\x10\x05\x12\x16\n\x12STATUS_INPUT_ERROR\x10\x06\x12\x14\n\x10STATUS_NOT_FOUND\x10\x07\x12\x15\n\x11STATUS_AUTH_ERROR\x10\x08\x32\xc1\x01\n\x07Scanner\x12U\n\x04Scan\x12$.gitlab.secret_detection.ScanRequest\x1a%.gitlab.secret_detection.ScanResponse\"\x00\x12_\n\nScanStream\x12$.gitlab.secret_detection.ScanRequest\x1a%.gitlab.secret_detection.ScanResponse\"\x00(\x01\x30\x01\x42 \xea\x02\x1dGitLab::SecretDetection::GRPCb\x06proto3"
|
9
9
|
|
10
10
|
pool = Google::Protobuf::DescriptorPool.generated_pool
|
11
11
|
pool.add_serialized_file(descriptor_data)
|
@@ -74,14 +74,19 @@ module GitLab
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
77
|
+
begin
|
78
|
+
result = scanner.secrets_scan(
|
79
|
+
payloads,
|
80
|
+
raw_value_exclusions:,
|
81
|
+
rule_exclusions:,
|
82
|
+
tags: request.tags.to_a,
|
83
|
+
timeout: request.timeout_secs,
|
84
|
+
payload_timeout: request.payload_timeout_secs
|
85
|
+
)
|
86
|
+
rescue StandardError => e
|
87
|
+
logger.error("Failed to run the scan: #{e}")
|
88
|
+
raise ::GRPC::Unknown, e.message
|
89
|
+
end
|
85
90
|
|
86
91
|
findings = result.results&.map do |finding|
|
87
92
|
GitLab::SecretDetection::GRPC::ScanResponse::Finding.new(**finding.to_h)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require_relative 'memoize'
|
5
|
+
|
6
|
+
module GitLab
|
7
|
+
module SecretDetection
|
8
|
+
module Utils
|
9
|
+
module X509
|
10
|
+
# Pulled from GitLab.com source
|
11
|
+
# Link: https://gitlab.com/gitlab-org/gitlab/-/blob/4713a798f997389f04e442db3d1d8349a39d5d46/lib/gitlab/x509/certificate.rb
|
12
|
+
class Certificate
|
13
|
+
CERT_REGEX = /-----BEGIN CERTIFICATE-----(?:.|\n)+?-----END CERTIFICATE-----/
|
14
|
+
|
15
|
+
attr_reader :key, :cert, :ca_certs
|
16
|
+
|
17
|
+
def self.default_cert_dir
|
18
|
+
strong_memoize(:default_cert_dir) do
|
19
|
+
ENV.fetch('SSL_CERT_DIR', OpenSSL::X509::DEFAULT_CERT_DIR)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.default_cert_file
|
24
|
+
strong_memoize(:default_cert_file) do
|
25
|
+
ENV.fetch('SSL_CERT_FILE', OpenSSL::X509::DEFAULT_CERT_FILE)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_strings(key_string, cert_string, ca_certs_string = nil)
|
30
|
+
key = OpenSSL::PKey::RSA.new(key_string)
|
31
|
+
cert = OpenSSL::X509::Certificate.new(cert_string)
|
32
|
+
ca_certs = load_ca_certs_bundle(ca_certs_string)
|
33
|
+
|
34
|
+
new(key, cert, ca_certs)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.from_files(key_path, cert_path, ca_certs_path = nil)
|
38
|
+
ca_certs_string = File.read(ca_certs_path) if ca_certs_path
|
39
|
+
|
40
|
+
from_strings(File.read(key_path), File.read(cert_path), ca_certs_string)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns all top-level, readable files in the default CA cert directory
|
44
|
+
def self.ca_certs_paths
|
45
|
+
cert_paths = Dir["#{default_cert_dir}/*"].select do |path|
|
46
|
+
!File.directory?(path) && File.readable?(path)
|
47
|
+
end
|
48
|
+
cert_paths << default_cert_file if File.exist? default_cert_file
|
49
|
+
cert_paths
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns a concatenated array of Strings, each being a PEM-coded CA certificate.
|
53
|
+
def self.ca_certs_bundle
|
54
|
+
strong_memoize(:ca_certs_bundle) do
|
55
|
+
ca_certs_paths.flat_map do |cert_file|
|
56
|
+
load_ca_certs_bundle(File.read(cert_file))
|
57
|
+
end.uniq.join("\n")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.reset_ca_certs_bundle
|
62
|
+
clear_memoization(:ca_certs_bundle)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.reset_default_cert_paths
|
66
|
+
clear_memoization(:default_cert_dir)
|
67
|
+
clear_memoization(:default_cert_file)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns an array of OpenSSL::X509::Certificate objects, empty array if none found
|
71
|
+
#
|
72
|
+
# Ruby OpenSSL::X509::Certificate.new will only load the first
|
73
|
+
# certificate if a bundle is presented, this allows to parse multiple certs
|
74
|
+
# in the same file
|
75
|
+
def self.load_ca_certs_bundle(ca_certs_string)
|
76
|
+
return [] unless ca_certs_string
|
77
|
+
|
78
|
+
ca_certs_string.scan(CERT_REGEX).map do |ca_cert_string|
|
79
|
+
OpenSSL::X509::Certificate.new(ca_cert_string)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(key, cert, ca_certs = nil)
|
84
|
+
@key = key
|
85
|
+
@cert = cert
|
86
|
+
@ca_certs = ca_certs
|
87
|
+
end
|
88
|
+
|
89
|
+
def key_string
|
90
|
+
key.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def cert_string
|
94
|
+
cert.to_pem
|
95
|
+
end
|
96
|
+
|
97
|
+
def ca_certs_string
|
98
|
+
ca_certs&.map(&:to_pem)&.join('\n') unless ca_certs.blank?
|
99
|
+
end
|
100
|
+
|
101
|
+
class << self
|
102
|
+
include ::GitLab::SecretDetection::Utils::StrongMemoize
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GitLab
|
4
|
+
module SecretDetection
|
5
|
+
module Utils
|
6
|
+
# Pulled from GitLab.com source
|
7
|
+
# Link: https://gitlab.com/gitlab-org/gitlab/-/blob/4713a798f997389f04e442db3d1d8349a39d5d46/gems/gitlab-utils/lib/gitlab/utils/strong_memoize.rb
|
8
|
+
module StrongMemoize
|
9
|
+
# Instead of writing patterns like this:
|
10
|
+
#
|
11
|
+
# def trigger_from_token
|
12
|
+
# return @trigger if defined?(@trigger)
|
13
|
+
#
|
14
|
+
# @trigger = Ci::Trigger.find_by_token(params[:token].to_s)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# We could write it like:
|
18
|
+
#
|
19
|
+
# include GitLab::SecretDetection::Utils::StrongMemoize
|
20
|
+
#
|
21
|
+
# def trigger_from_token
|
22
|
+
# Ci::Trigger.find_by_token(params[:token].to_s)
|
23
|
+
# end
|
24
|
+
# strong_memoize_attr :trigger_from_token
|
25
|
+
#
|
26
|
+
# def enabled?
|
27
|
+
# Feature.enabled?(:some_feature)
|
28
|
+
# end
|
29
|
+
# strong_memoize_attr :enabled?
|
30
|
+
#
|
31
|
+
def strong_memoize(name)
|
32
|
+
key = ivar(name)
|
33
|
+
|
34
|
+
if instance_variable_defined?(key)
|
35
|
+
instance_variable_get(key)
|
36
|
+
else
|
37
|
+
instance_variable_set(key, yield)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Works the same way as "strong_memoize" but takes
|
42
|
+
# a second argument - expire_in. This allows invalidate
|
43
|
+
# the data after specified number of seconds
|
44
|
+
def strong_memoize_with_expiration(name, expire_in)
|
45
|
+
key = ivar(name)
|
46
|
+
expiration_key = "#{key}_expired_at"
|
47
|
+
|
48
|
+
if instance_variable_defined?(expiration_key)
|
49
|
+
expire_at = instance_variable_get(expiration_key)
|
50
|
+
clear_memoization(name) if expire_at.past?
|
51
|
+
end
|
52
|
+
|
53
|
+
if instance_variable_defined?(key)
|
54
|
+
instance_variable_get(key)
|
55
|
+
else
|
56
|
+
value = instance_variable_set(key, yield)
|
57
|
+
instance_variable_set(expiration_key, Time.current + expire_in)
|
58
|
+
value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def strong_memoize_with(name, *args)
|
63
|
+
container = strong_memoize(name) { {} }
|
64
|
+
|
65
|
+
if container.key?(args)
|
66
|
+
container[args]
|
67
|
+
else
|
68
|
+
container[args] = yield
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def strong_memoized?(name)
|
73
|
+
key = ivar(StrongMemoize.normalize_key(name))
|
74
|
+
instance_variable_defined?(key)
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear_memoization(name)
|
78
|
+
key = ivar(StrongMemoize.normalize_key(name))
|
79
|
+
remove_instance_variable(key) if instance_variable_defined?(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
module StrongMemoizeClassMethods
|
83
|
+
def strong_memoize_attr(method_name)
|
84
|
+
member_name = StrongMemoize.normalize_key(method_name)
|
85
|
+
|
86
|
+
StrongMemoize.send(:do_strong_memoize, self, method_name, member_name) # rubocop:disable GitlabSecurity/PublicSend
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.included(base)
|
91
|
+
base.singleton_class.prepend(StrongMemoizeClassMethods)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Convert `"name"`/`:name` into `:@name`
|
97
|
+
#
|
98
|
+
# Depending on a type ensure that there's a single memory allocation
|
99
|
+
def ivar(name)
|
100
|
+
case name
|
101
|
+
when Symbol
|
102
|
+
name.to_s.prepend("@").to_sym
|
103
|
+
when String
|
104
|
+
:"@#{name}"
|
105
|
+
else
|
106
|
+
raise ArgumentError, "Invalid type of '#{name}'"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class << self
|
111
|
+
def normalize_key(key)
|
112
|
+
return key unless key.end_with?('!', '?')
|
113
|
+
|
114
|
+
# Replace invalid chars like `!` and `?` with allowed Unicode codeparts.
|
115
|
+
key.to_s.tr('!?', "\uFF01\uFF1F")
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def do_strong_memoize(klass, method_name, member_name)
|
121
|
+
method = klass.instance_method(method_name)
|
122
|
+
|
123
|
+
unless method.arity.zero?
|
124
|
+
raise <<~ERROR
|
125
|
+
Using `strong_memoize_attr` on methods with parameters is not supported.
|
126
|
+
|
127
|
+
Use `strong_memoize_with` instead.
|
128
|
+
See https://docs.gitlab.com/ee/development/utilities.html#strongmemoize
|
129
|
+
ERROR
|
130
|
+
end
|
131
|
+
|
132
|
+
# Methods defined within a class method are already public by default, so we don't need to
|
133
|
+
# explicitly make them public.
|
134
|
+
scope = %i[private protected].find do |scope|
|
135
|
+
klass.send(:"#{scope}_instance_methods") # rubocop:disable GitlabSecurity/PublicSend
|
136
|
+
.include? method_name
|
137
|
+
end
|
138
|
+
|
139
|
+
klass.define_method(method_name) do |&block|
|
140
|
+
strong_memoize(member_name) do
|
141
|
+
method.bind_call(self, &block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
klass.send(scope, method_name) if scope # rubocop:disable GitlabSecurity/PublicSend
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -5,7 +5,7 @@ package gitlab.secret_detection;
|
|
5
5
|
/* We keep generated files within grpc namespace i.e GitLab::SecretDetection::GRPC
|
6
6
|
* so that these files are exported too in the Ruby Gem along with Core and GRPC logic.
|
7
7
|
*/
|
8
|
-
option ruby_package="GitLab::SecretDetection::GRPC";
|
8
|
+
option ruby_package = "GitLab::SecretDetection::GRPC";
|
9
9
|
|
10
10
|
/* Request arg for triggering Scan/ScanStream method */
|
11
11
|
message ScanRequest {
|
@@ -42,11 +42,10 @@ message ScanResponse {
|
|
42
42
|
// Represents a secret finding identified within a payload
|
43
43
|
message Finding {
|
44
44
|
string payload_id = 1;
|
45
|
-
|
45
|
+
int32 status = 2;
|
46
46
|
optional string type = 3;
|
47
47
|
optional string description = 4;
|
48
48
|
optional int32 line_number = 5;
|
49
|
-
optional string error = 6;
|
50
49
|
}
|
51
50
|
|
52
51
|
// Return status code in sync with ::SecretDetection::Status
|
@@ -59,18 +58,18 @@ message ScanResponse {
|
|
59
58
|
STATUS_SCAN_ERROR = 5; // internal scan failure
|
60
59
|
STATUS_INPUT_ERROR = 6; // invalid input failure
|
61
60
|
STATUS_NOT_FOUND = 7; // zero findings
|
61
|
+
STATUS_AUTH_ERROR = 8; // authentication failure
|
62
62
|
}
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
Status status = 3;
|
64
|
+
repeated Finding results = 1;
|
65
|
+
int32 status = 2;
|
67
66
|
}
|
68
67
|
|
69
68
|
/* Scanner service that scans given payloads and returns findings */
|
70
69
|
service Scanner {
|
71
70
|
// Runs secret detection scan for the given request
|
72
|
-
rpc Scan(ScanRequest) returns (ScanResponse) {
|
71
|
+
rpc Scan(ScanRequest) returns (ScanResponse) {}
|
73
72
|
|
74
73
|
// Runs bi-directional streaming of scans for the given stream of requests with a stream of responses
|
75
|
-
rpc ScanStream(stream ScanRequest) returns (stream ScanResponse) {
|
74
|
+
rpc ScanStream(stream ScanRequest) returns (stream ScanResponse) {}
|
76
75
|
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-secret_detection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- group::secret detection
|
@@ -10,70 +10,64 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-10-04 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: grpc
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - '='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 1.63.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
|
-
- -
|
26
|
+
- - '='
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
28
|
+
version: 1.63.0
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: grpc-tools
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
|
-
- -
|
33
|
+
- - '='
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version:
|
35
|
+
version: 1.63.0
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
|
-
- -
|
40
|
+
- - '='
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
42
|
+
version: 1.63.0
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: re2
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- -
|
47
|
+
- - '='
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
49
|
+
version: 2.7.0
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- -
|
54
|
+
- - '='
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
56
|
+
version: 2.7.0
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: toml-rb
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
64
|
-
- - ">="
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: 3.0.1
|
63
|
+
version: 2.2.0
|
67
64
|
type: :runtime
|
68
65
|
prerelease: false
|
69
66
|
version_requirements: !ruby/object:Gem::Requirement
|
70
67
|
requirements:
|
71
68
|
- - "~>"
|
72
69
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: 3.0.1
|
70
|
+
version: 2.2.0
|
77
71
|
description: |-
|
78
72
|
GitLab Secret Detection gem accepts text-based payloads, matches them against predefined secret
|
79
73
|
detection rules (based on the ruleset used by GitLab Secrets analyzer), and returns the scan results. The gem also
|
@@ -99,10 +93,14 @@ files:
|
|
99
93
|
- lib/gitlab/secret_detection/core/status.rb
|
100
94
|
- lib/gitlab/secret_detection/grpc.rb
|
101
95
|
- lib/gitlab/secret_detection/grpc/client/grpc_client.rb
|
96
|
+
- lib/gitlab/secret_detection/grpc/client/stream_request_enumerator.rb
|
102
97
|
- lib/gitlab/secret_detection/grpc/generated/.gitkeep
|
103
98
|
- lib/gitlab/secret_detection/grpc/generated/secret_detection_pb.rb
|
104
99
|
- lib/gitlab/secret_detection/grpc/generated/secret_detection_services_pb.rb
|
105
100
|
- lib/gitlab/secret_detection/grpc/scanner_service.rb
|
101
|
+
- lib/gitlab/secret_detection/utils.rb
|
102
|
+
- lib/gitlab/secret_detection/utils/certificate.rb
|
103
|
+
- lib/gitlab/secret_detection/utils/memoize.rb
|
106
104
|
- lib/gitlab/secret_detection/version.rb
|
107
105
|
- proto/secret_detection.proto
|
108
106
|
homepage: https://gitlab.com/gitlab-org/security-products/secret-detection/secret-detection-service
|