Skip to content

Commit

Permalink
Merge pull request #2771 from DataDog/remote-populate-config-states
Browse files Browse the repository at this point in the history
[APPSEC-8957] Populate remote client cached_target_files payload field
  • Loading branch information
GustavoCaso authored Apr 13, 2023
2 parents efa9fb6 + 518b86d commit f523ddd
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 88 deletions.
18 changes: 17 additions & 1 deletion lib/datadog/core/remote/configuration/content.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require_relative 'path'
require_relative 'digest'

module Datadog
module Core
Expand All @@ -17,11 +18,26 @@ def parse(hash)
end
end

attr_reader :path, :data
attr_reader :path, :data, :hashes

def initialize(path:, data:)
@path = path
@data = data
@hashes = {}
end

def hexdigest(type)
@hashes[type] || compute_and_store_hash(type)
end

def length
@length ||= @data.size
end

private

def compute_and_store_hash(type)
@hashes[type] = Digest.hexdigest(type, @data)
end

private_class_method :new
Expand Down
60 changes: 60 additions & 0 deletions lib/datadog/core/remote/configuration/digest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module Datadog
module Core
module Remote
class Configuration
# Represent a list of Configuration::Digest
class DigestList < Array
class << self
def parse(hash)
new.concat(hash.map { |type, hexdigest| Digest.new(type, hexdigest) })
end
end

def check(content)
map { |digest| digest.check(content) }.reduce(:&)
end
end

# Stores and validates different cryptographic hash functions
class Digest
class InvalidHashTypeError < StandardError; end
attr_reader :type, :hexdigest

DIGEST_CHUNK = 1024

class << self
def hexdigest(type, data)
d = case type
when :sha256
::Digest::SHA256.new
when :sha512
::Digest::SHA512.new
else
raise InvalidHashTypeError, type
end

while (buf = data.read(DIGEST_CHUNK))
d.update(buf)
end

d.hexdigest
ensure
data.rewind
end
end

def initialize(type, hexdigest)
@type = type.to_sym
@hexdigest = hexdigest
end

def check(content)
content.hexdigest(@type) == hexdigest
end
end
end
end
end
end
23 changes: 22 additions & 1 deletion lib/datadog/core/remote/configuration/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Repository
:targets_version

UNVERIFIED_ROOT_VERSION = 1

INITIAL_TARGETS_VERSION = 0

def initialize
Expand Down Expand Up @@ -73,13 +74,33 @@ class State
:opaque_backend_state

def initialize(repository)
@repository = repository
@root_version = repository.root_version
@targets_version = repository.targets_version
@config_states = []
@config_states = contents_to_config_states(repository.contents)
@has_error = false
@error = ''
@opaque_backend_state = repository.opaque_backend_state
end

private

def contents_to_config_states(contents)
return [] if contents.empty?

contents.map do |content|
{
path: content.path.to_s,
length: content.length,
hashes: content.hashes.map do |algorithm, hexdigest|
{
algorithm: algorithm,
hash: hexdigest
}
end
}
end
end
end

# Encapsulates transaction operations
Expand Down
58 changes: 3 additions & 55 deletions lib/datadog/core/remote/configuration/target.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require 'digest/sha2'
require_relative 'path'
require_relative 'digest'

module Datadog
module Core
Expand Down Expand Up @@ -47,7 +47,7 @@ class Target
class << self
def parse(hash)
length = Integer(hash['length'])
digests = DigestList.parse(hash['hashes'])
digests = Configuration::DigestList.parse(hash['hashes'])

new(digests: digests, length: length)
end
Expand All @@ -63,60 +63,8 @@ def initialize(digests:, length:)
private_class_method :new

def check(content)
digests.check(content.data)
digests.check(content)
end

# Represent a list of Configuration::Target::Digest
class DigestList < Array
class << self
def parse(hash)
new.concat(hash.map { |type, hexdigest| Digest.new(type, hexdigest) })
end
end

def check(data)
map { |digest| digest.check(data) }.reduce(:&)
end
end

private_constant :DigestList

# Stores and validates different cryptographic hash functions
class Digest
attr_reader :type, :hexdigest

def initialize(type, hexdigest)
@type = type.to_sym
@hexdigest = hexdigest
end

def check(data)
case @type
when :sha256
chunked_hexdigest(data) == hexdigest
else
raise
end
ensure
data.rewind
end

private

DIGEST_CHUNK = 1024

def chunked_hexdigest(io)
d = ::Digest::SHA256.new

while (buf = io.read(DIGEST_CHUNK))
d.update(buf)
end

d.hexdigest
end
end

private_constant :DigestList
end
end
end
Expand Down
12 changes: 12 additions & 0 deletions sig/datadog/core/remote/configuration/content.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@ module Datadog

attr_reader data: StringIO

attr_reader hashes: Hash[Symbol, String]

@length: Integer

def initialize: (path: Configuration::Path, data: StringIO) -> void

def hexdigest: (Symbol type) -> String

def length: () -> Integer

private

def compute_and_store_hash: (Symbol type) -> String
end

class ContentList < Array[Content]
Expand Down
30 changes: 30 additions & 0 deletions sig/datadog/core/remote/configuration/digest.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module Datadog
module Core
module Remote
class Configuration
class DigestList < Array[Digest]
def self.parse: (::Hash[::String, untyped] hash) -> DigestList

def check: (Datadog::Core::Remote::Configuration::Content content) -> bool
end

class Digest
class InvalidHashTypeError < StandardError
end

DIGEST_CHUNK: 1024

attr_reader type: ::Symbol

attr_reader hexdigest: ::String

def self.hexdigest: (Symbol type, StringIO data) -> String

def initialize: (::String type, ::String hexdigest) -> void

def check: (Datadog::Core::Remote::Configuration::Content content) -> bool
end
end
end
end
end
8 changes: 7 additions & 1 deletion sig/datadog/core/remote/configuration/repository.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ module Datadog

attr_reader targets_version: Integer

attr_reader config_states: Array[untyped]
attr_reader repository: Repository

attr_reader config_states: Array[Hash[Symbol, untyped]]

attr_reader has_error: bool

Expand All @@ -41,6 +43,10 @@ module Datadog
attr_reader opaque_backend_state: String?

def initialize: (Repository repository) -> void

private

def contents_to_config_states: (ContentList contents) -> Array[Hash[Symbol, untyped]]
end

class Transaction
Expand Down
22 changes: 0 additions & 22 deletions sig/datadog/core/remote/configuration/target.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,6 @@ module Datadog
def initialize: (digests: DigestList, length: ::Integer) -> void

def check: (untyped content) -> bool

class DigestList < Array[Digest]
def self.parse: (::Hash[::String, untyped] hash) -> DigestList

def check: (StringIO data) -> bool
end

class Digest
attr_reader type: ::Symbol

attr_reader hexdigest: ::String

def initialize: (::String type, ::String hexdigest) -> void

def check: (StringIO data) -> bool

private

DIGEST_CHUNK: 1024

def chunked_hexdigest: (StringIO io) -> ::String
end
end
end
end
Expand Down
42 changes: 42 additions & 0 deletions spec/datadog/core/remote/configuration/content_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,46 @@
expect(paths[0].to_s).to eq('datadog/603646/ASM/exclusion_filters/config')
end
end

describe Datadog::Core::Remote::Configuration::Content do
subject(:content) do
described_class.parse(
{
:path => path.to_s,
:content => string_io_content
}
)
end

describe '#hashes' do
context 'when no hash has been computed' do
it 'return {}' do
expect(content.hashes).to eq({})
end
end
end

describe '#hexdigest' do
before do
content.hexdigest(:sha256)
content.hexdigest(:sha512)
end

context 'compute hash of content' do
it 'returns hash value' do
expect(content.hexdigest(:sha256)).to eq('c8358ce9038693fb74ad8625e4c6c563bd2afb16b4412b2c8f7dba062e9e88de')
end

it 'stores the value in hashes' do
expect(content.hashes).to eq(
{
:sha256 => 'c8358ce9038693fb74ad8625e4c6c563bd2afb16b4412b2c8f7dba062e9e88de',
:sha512 => '546b5325ec8559dda0b34f3e628e99c7b9d18eb59b23ec87f672b1ed8c4ac9ac'\
'11ac6ffb15e6b4d71f5f343ec243d142db61aaf60f4a0410e39dc916c623cc82'
}
)
end
end
end
end
end
Loading

0 comments on commit f523ddd

Please sign in to comment.