mirror of https://github.com/grpc/grpc.git
[Security - Revocation] Crl Directory Watcher Implementation (#34749)
This adds the directory reloader implementation of the CrlProvider. This will periodically reload CRL files in a directory per [gRFC A69](https://github.com/grpc/proposal/pull/382) Included in this is the following: * A public API to create the `DirectoryReloaderCrlProvider` * A basic directory interface in gprpp and platform specific impls for getting the list of files in a directory (unfortunately prior C++17, there is no std::filesystem, so we have to have platform specific impls) * The implementation of `DirectoryReloaderCrlProvider` takes an event_engine and a directory interface. This allows us to test using the fuzzing event engine for time mocking, and to implement a test directory interface so we avoid having to make temporary directories and files in the tests. This is notably not in `include`, and the `CreateDirectoryReloaderCrlProvider` is the only way to construct one from the public API, so we don't expose the event engine and directory details to the user. --------- Co-authored-by: gtcooke94 <gtcooke94@users.noreply.github.com>pull/34881/head
parent
2e205d8fbf
commit
0d4e1ef5df
26 changed files with 832 additions and 17 deletions
@ -0,0 +1,48 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef GRPC_SRC_CORE_LIB_GPRPP_DIRECTORY_READER_H |
||||
#define GRPC_SRC_CORE_LIB_GPRPP_DIRECTORY_READER_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <memory> |
||||
|
||||
#include "absl/functional/function_ref.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
class DirectoryReader { |
||||
public: |
||||
virtual ~DirectoryReader() = default; |
||||
// Returns the name of the directory being read.
|
||||
virtual absl::string_view Name() const = 0; |
||||
// Calls callback for each name in the directory except for "." and "..".
|
||||
// Returns non-OK if there was an error reading the directory.
|
||||
virtual absl::Status ForEach( |
||||
absl::FunctionRef<void(absl::string_view)> callback) = 0; |
||||
}; |
||||
|
||||
std::unique_ptr<DirectoryReader> MakeDirectoryReader( |
||||
absl::string_view filename); |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GRPC_SRC_CORE_LIB_GPRPP_DIRECTORY_READER_H
|
@ -0,0 +1,82 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <memory> |
||||
|
||||
#include "absl/functional/function_ref.h" |
||||
#include "absl/status/status.h" |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
#if defined(GPR_LINUX) || defined(GPR_ANDROID) || defined(GPR_FREEBSD) || \ |
||||
defined(GPR_APPLE) |
||||
|
||||
#include <dirent.h> |
||||
|
||||
#include <string> |
||||
|
||||
#include "src/core/lib/gprpp/directory_reader.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
const char kSkipEntriesSelf[] = "."; |
||||
const char kSkipEntriesParent[] = ".."; |
||||
} // namespace
|
||||
|
||||
class DirectoryReaderImpl : public DirectoryReader { |
||||
public: |
||||
explicit DirectoryReaderImpl(absl::string_view directory_path) |
||||
: directory_path_(directory_path) {} |
||||
absl::string_view Name() const override { return directory_path_; } |
||||
absl::Status ForEach(absl::FunctionRef<void(absl::string_view)>) override; |
||||
|
||||
private: |
||||
const std::string directory_path_; |
||||
}; |
||||
|
||||
std::unique_ptr<DirectoryReader> MakeDirectoryReader( |
||||
absl::string_view filename) { |
||||
return std::make_unique<DirectoryReaderImpl>(filename); |
||||
} |
||||
|
||||
absl::Status DirectoryReaderImpl::ForEach( |
||||
absl::FunctionRef<void(absl::string_view)> callback) { |
||||
// Open the dir for reading
|
||||
DIR* directory = opendir(directory_path_.c_str()); |
||||
if (directory == nullptr) { |
||||
return absl::InternalError("Could not read crl directory."); |
||||
} |
||||
struct dirent* directory_entry; |
||||
// Iterate over everything in the directory
|
||||
while ((directory_entry = readdir(directory)) != nullptr) { |
||||
const absl::string_view file_name = directory_entry->d_name; |
||||
// Skip "." and ".."
|
||||
if (file_name == kSkipEntriesParent || file_name == kSkipEntriesSelf) { |
||||
continue; |
||||
} |
||||
// Call the callback with this filename
|
||||
callback(file_name); |
||||
} |
||||
closedir(directory); |
||||
return absl::OkStatus(); |
||||
} |
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GPR_LINUX || GPR_ANDROID || GPR_FREEBSD || GPR_APPLE
|
@ -0,0 +1,80 @@ |
||||
//
|
||||
//
|
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//
|
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#if defined(GPR_WINDOWS) |
||||
|
||||
#include <sys/stat.h> |
||||
#include <windows.h> |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/status/statusor.h" |
||||
#include "absl/strings/string_view.h" |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/gprpp/directory_reader.h" |
||||
namespace grpc_core { |
||||
|
||||
namespace { |
||||
const char kSkipEntriesSelf[] = "."; |
||||
const char kSkipEntriesParent[] = ".."; |
||||
} // namespace
|
||||
|
||||
class DirectoryReaderImpl : public DirectoryReader { |
||||
public: |
||||
explicit DirectoryReaderImpl(absl::string_view directory_path) |
||||
: directory_path_(directory_path) {} |
||||
absl::string_view Name() const override { return directory_path_; } |
||||
absl::Status ForEach(absl::FunctionRef<void(absl::string_view)>) override; |
||||
|
||||
private: |
||||
const std::string directory_path_; |
||||
}; |
||||
|
||||
std::unique_ptr<DirectoryReader> MakeDirectoryReader( |
||||
absl::string_view filename) { |
||||
return std::make_unique<DirectoryReaderImpl>(filename); |
||||
} |
||||
|
||||
// Reference for reading directory in Windows:
|
||||
// https://stackoverflow.com/questions/612097/how-can-i-get-the-list-of-files-in-a-directory-using-c-or-c
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/listing-the-files-in-a-directory
|
||||
absl::Status DirectoryReaderImpl::ForEach( |
||||
absl::FunctionRef<void(absl::string_view)> callback) { |
||||
std::string search_path = absl::StrCat(directory_path_, "/*"); |
||||
WIN32_FIND_DATAA find_data; |
||||
HANDLE hFind = ::FindFirstFileA(search_path.c_str(), &find_data); |
||||
if (hFind == INVALID_HANDLE_VALUE) { |
||||
return absl::InternalError("Could not read crl directory."); |
||||
} |
||||
do { |
||||
if (!(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { |
||||
callback(find_data.cFileName); |
||||
} |
||||
} while (::FindNextFileA(hFind, &find_data)); |
||||
::FindClose(hFind); |
||||
return absl::OkStatus(); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif // GPR_WINDOWS
|
@ -0,0 +1,64 @@ |
||||
//
|
||||
// Copyright 2023 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
#include "src/core/lib/gprpp/directory_reader.h" |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "absl/strings/string_view.h" |
||||
#include "gmock/gmock.h" |
||||
#include "gtest/gtest.h" |
||||
|
||||
#include "test/core/util/test_config.h" |
||||
|
||||
static constexpr absl::string_view kCrlDirectory = |
||||
"test/core/tsi/test_creds/crl_data/crls/"; |
||||
|
||||
namespace grpc_core { |
||||
namespace testing { |
||||
namespace { |
||||
|
||||
TEST(DirectoryReader, CanListFiles) { |
||||
auto reader = MakeDirectoryReader(kCrlDirectory); |
||||
std::vector<std::string> contents; |
||||
absl::Status status = reader->ForEach([&](absl::string_view filename) { |
||||
contents.push_back(std::string(filename)); |
||||
}); |
||||
ASSERT_TRUE(status.ok()) << status; |
||||
// IsSupersetOf() is needed instead of UnorderedElementsAre() because some
|
||||
// builds/OS combinations will include the BUILD file in this directory when
|
||||
// the tests are run
|
||||
EXPECT_THAT(contents, |
||||
::testing::IsSupersetOf({"ab06acdd.r0", "b9322cac.r0", |
||||
"current.crl", "intermediate.crl"})); |
||||
} |
||||
|
||||
TEST(DirectoryReader, NonexistentDirectory) { |
||||
auto reader = MakeDirectoryReader("DOES_NOT_EXIST"); |
||||
absl::Status status = reader->ForEach([](absl::string_view) {}); |
||||
ASSERT_FALSE(status.ok()) << status; |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
grpc::testing::TestEnvironment env(&argc, argv); |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue