You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by GitBox <gi...@apache.org> on 2020/11/17 17:04:38 UTC

[GitHub] [nifi-minifi-cpp] fgerlits commented on a change in pull request #937: MINIFICPP-1402 - Encrypt flow configuration and change encryption key

fgerlits commented on a change in pull request #937:
URL: https://github.com/apache/nifi-minifi-cpp/pull/937#discussion_r524435300



##########
File path: encrypt-config/ArgParser.cpp
##########
@@ -0,0 +1,192 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 <string>
+#include <set>
+#include <iostream>
+#include <algorithm>
+#include "ArgParser.h"
+#include "utils/OptionalUtils.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace encrypt_config {
+
+const std::vector<Argument> Arguments::simple_arguments_{
+    {std::vector<std::string>{"--minifi-home", "-m"},
+     true,
+     "minifi home",
+     "Specifies the home directory used by the minifi agent"}
+};
+
+const std::vector<FlagArgument> Arguments::flag_arguments_{
+    {std::vector<std::string>{"--help", "-h"},
+     "Prints this help message"},
+    {std::vector<std::string>{"--encrypt-flow-config"},
+     "If set, the flow configuration file (as specified in minifi.properties) is also encrypted."}
+};
+
+std::string Arguments::getHelp() {
+  std::stringstream ss;
+  ss << "Usage: " << "encrypt-config";
+  for (const auto& simple_arg : simple_arguments_) {
+    ss << " ";
+    if (!simple_arg.required) {
+      ss << "[";
+    }
+    ss << utils::StringUtils::join("|", simple_arg.names)
+        << " <" << simple_arg.value_name << ">";
+    if (!simple_arg.required) {
+      ss << "]";
+    }
+  }
+  for (const auto& flag : flag_arguments_) {
+    ss << " [" << utils::StringUtils::join("|", flag.names) << "]";
+  }
+  ss << std::endl;
+  for (const auto& simple_arg : simple_arguments_) {
+    ss << "\t";
+    ss << utils::StringUtils::join("|", simple_arg.names) << " : ";
+    if (simple_arg.required) {
+      ss << "(required)";
+    } else {
+      ss << "(optional)";
+    }
+    ss << " " << simple_arg.description;
+    ss << std::endl;
+  }
+  for (const auto& flag : flag_arguments_) {
+    ss << "\t" << utils::StringUtils::join("|", flag.names) << " : "
+        << flag.description << std::endl;
+  }
+  return ss.str();
+}
+
+void Arguments::set(const std::string& key, const std::string& value) {
+  if (get(key)) {
+    std::cerr << "Key is specified more that once \"" << key << "\"" << std::endl;

Review comment:
       typo: that -> than (also in the flag version of `set()`)

##########
File path: encrypt-config/EncryptConfig.cpp
##########
@@ -42,40 +42,72 @@ namespace nifi {
 namespace minifi {
 namespace encrypt_config {
 
-EncryptConfig::EncryptConfig(int argc, char* argv[]) : minifi_home_(parseMinifiHomeFromTheOptions(argc, argv)) {
+EncryptConfig::EncryptConfig(const std::string& minifi_home) : minifi_home_(minifi_home) {
   if (sodium_init() < 0) {
     throw std::runtime_error{"Could not initialize the libsodium library!"};
   }
+  keys_ = getEncryptionKeys();
 }
 
-std::string EncryptConfig::parseMinifiHomeFromTheOptions(int argc, char* argv[]) {
-  if (argc >= 2) {
-    for (int i = 1; i < argc; ++i) {
-      std::string argstr(argv[i]);
-      if ((argstr == "-h") || (argstr == "--help")) {
-        std::cout << USAGE_STRING << std::endl;
-        std::exit(0);
-      }
-    }
+EncryptConfig::EncryptionType EncryptConfig::encryptSensitiveProperties() const {
+  encryptSensitiveProperties(keys_);
+  if (keys_.decryption_key) {
+    return EncryptionType::RE_ENCRYPT;
   }
+  return EncryptionType::ENCRYPT;
+}
 
-  if (argc >= 3) {
-    for (int i = 1; i < argc; ++i) {
-      std::string argstr(argv[i]);
-      if ((argstr == "-m") || (argstr == "--minifi-home")) {
-        if (i+1 < argc) {
-          return std::string(argv[i+1]);
-        }
-      }
-    }
+void EncryptConfig::encryptFlowConfig() const {
+  encrypt_config::ConfigFile properties_file{std::ifstream{propertiesFilePath()}};
+  utils::optional<std::string> config_path = properties_file.getValue(Configure::nifi_flow_configuration_file);
+  if (!config_path) {
+    config_path = utils::file::PathUtils::resolve(minifi_home_, "conf/config.yml");

Review comment:
       you could use the `DEFAULT_NIFI_CONFIG_YML` constant defined in MainHelper.h

##########
File path: encrypt-config/ArgParser.cpp
##########
@@ -0,0 +1,192 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 <string>
+#include <set>
+#include <iostream>
+#include <algorithm>
+#include "ArgParser.h"
+#include "utils/OptionalUtils.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace encrypt_config {
+
+const std::vector<Argument> Arguments::simple_arguments_{
+    {std::vector<std::string>{"--minifi-home", "-m"},
+     true,
+     "minifi home",
+     "Specifies the home directory used by the minifi agent"}
+};
+
+const std::vector<FlagArgument> Arguments::flag_arguments_{
+    {std::vector<std::string>{"--help", "-h"},
+     "Prints this help message"},
+    {std::vector<std::string>{"--encrypt-flow-config"},
+     "If set, the flow configuration file (as specified in minifi.properties) is also encrypted."}
+};
+
+std::string Arguments::getHelp() {
+  std::stringstream ss;
+  ss << "Usage: " << "encrypt-config";
+  for (const auto& simple_arg : simple_arguments_) {
+    ss << " ";
+    if (!simple_arg.required) {
+      ss << "[";
+    }
+    ss << utils::StringUtils::join("|", simple_arg.names)
+        << " <" << simple_arg.value_name << ">";
+    if (!simple_arg.required) {
+      ss << "]";
+    }
+  }
+  for (const auto& flag : flag_arguments_) {
+    ss << " [" << utils::StringUtils::join("|", flag.names) << "]";
+  }
+  ss << std::endl;
+  for (const auto& simple_arg : simple_arguments_) {
+    ss << "\t";
+    ss << utils::StringUtils::join("|", simple_arg.names) << " : ";
+    if (simple_arg.required) {
+      ss << "(required)";
+    } else {
+      ss << "(optional)";
+    }
+    ss << " " << simple_arg.description;
+    ss << std::endl;
+  }
+  for (const auto& flag : flag_arguments_) {
+    ss << "\t" << utils::StringUtils::join("|", flag.names) << " : "
+        << flag.description << std::endl;
+  }
+  return ss.str();
+}
+
+void Arguments::set(const std::string& key, const std::string& value) {
+  if (get(key)) {
+    std::cerr << "Key is specified more that once \"" << key << "\"" << std::endl;
+    std::cerr << getHelp();
+    std::exit(1);
+  }
+  simple_args_[key] = value;
+}
+
+void Arguments::set(const std::string& flag) {
+  if (isSet(flag)) {
+    std::cerr << "Flag is specified more that once \"" << flag << "\"" << std::endl;
+    std::cerr << getHelp();
+    std::exit(1);
+  }
+  flag_args_.insert(flag);
+}
+
+utils::optional<std::string> Arguments::get(const std::string &key) const {
+  utils::optional<Argument> opt_arg = getSimpleArg(key);
+  if (!opt_arg) {
+    return {};
+  }
+  for (const auto& name : opt_arg->names) {
+    auto it = simple_args_.find(name);
+    if (it != simple_args_.end()) {
+      return it->second;
+    }
+  }
+  return {};
+}
+
+bool Arguments::isSet(const std::string &flag) const {
+  utils::optional<FlagArgument> opt_flag = getFlag(flag);
+  if (!opt_flag) {
+    return false;
+  }
+  return std::any_of(opt_flag->names.begin(), opt_flag->names.end(), [&] (const std::string& name) {
+    return flag_args_.find(name) != flag_args_.end();
+  });
+}
+
+Arguments Arguments::parse(int argc, char* argv[]) {
+  Arguments args;
+  for (int argIdx = 1; argIdx < argc; ++argIdx) {
+    std::string key{argv[argIdx]};
+    if (getFlag(key)) {
+      args.set(key);
+      continue;
+    }
+    if (!getSimpleArg(key)) {
+      std::cerr << "Unrecognized option: \"" << key << "\"" << std::endl;
+      std::cerr << getHelp();
+      std::exit(1);
+    }
+    if (argIdx == argc - 1) {
+      std::cerr << "No value specified for key \"" << key << "\"" << std::endl;
+      std::cerr << getHelp();
+      std::exit(1);
+    }
+    ++argIdx;
+    std::string value{argv[argIdx]};
+    args.set(key, value);
+  }
+  if (args.isSet("-h")) {
+    std::cout << getHelp();
+    std::exit(0);
+  }
+  for (const auto& simple_arg : simple_arguments_) {
+    if (simple_arg.required) {
+      bool found = false;
+      for (const auto& name : simple_arg.names) {
+        if (args.get(name)) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {

Review comment:
       ```suggestion
         if (std::none_of(simple_arg.names.begin(), simple_arg.names.end(), [](const std::string& name) {
             return bool{args.get(name)};
         }) {
   ```

##########
File path: encrypt-config/EncryptConfig.cpp
##########
@@ -90,33 +122,44 @@ std::string EncryptConfig::propertiesFilePath() const {
       MINIFI_PROPERTIES_FILE_NAME);
 }
 
-utils::crypto::Bytes EncryptConfig::getEncryptionKey() const {
+utils::crypto::EncryptionKeys EncryptConfig::getEncryptionKeys() const {
   encrypt_config::ConfigFile bootstrap_file{std::ifstream{bootstrapFilePath()}};
-  utils::optional<std::string> key_from_bootstrap_file = bootstrap_file.getValue(ENCRYPTION_KEY_PROPERTY_NAME);
+  utils::optional<std::string> decryption_key_hex = bootstrap_file.getValue(DECRYPTION_KEY_PROPERTY_NAME);
+  utils::optional<std::string> encryption_key_hex = bootstrap_file.getValue(ENCRYPTION_KEY_PROPERTY_NAME);
+
+  utils::crypto::EncryptionKeys keys;
+  if (!decryption_key_hex || decryption_key_hex->empty()) {
+    std::cout << "No decryption key was provided\n";

Review comment:
       I would not log this, as the user should not have to know what a "decryption key" is until they need/want to update the key.
   
   Also, it may not be obvious to the user that "old/deprecated key" and "decryption key" are the same thing; it would be good to use one consistent name for this in output messages (and the documentation).

##########
File path: encrypt-config/EncryptConfig.cpp
##########
@@ -42,40 +42,72 @@ namespace nifi {
 namespace minifi {
 namespace encrypt_config {
 
-EncryptConfig::EncryptConfig(int argc, char* argv[]) : minifi_home_(parseMinifiHomeFromTheOptions(argc, argv)) {
+EncryptConfig::EncryptConfig(const std::string& minifi_home) : minifi_home_(minifi_home) {
   if (sodium_init() < 0) {
     throw std::runtime_error{"Could not initialize the libsodium library!"};
   }
+  keys_ = getEncryptionKeys();

Review comment:
       I would put a comment here to explain why this is not an initializer.  Alternatively, maybe move the libsodium initialization to `main()`, and do use an initializer?

##########
File path: libminifi/include/utils/StringUtils.h
##########
@@ -149,6 +150,17 @@ class StringUtils {
     return std::equal(endString.rbegin(), endString.rend(), value.rbegin());
   }
 
+  inline static bool startsWith(const std::string &value, const std::string & startString) {

Review comment:
       just a suggestion, but these `startsWith` functions feel like they would better belong in `StringView`: `StringView{my_string}.startsWith(prefix)` -- that way, it would be clearer which parameter is the tested string, and which is the prefix

##########
File path: libminifi/include/utils/StringView.h
##########
@@ -0,0 +1,122 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <iterator>
+#include <algorithm>
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace utils {
+
+class StringView {
+ public:
+  constexpr StringView(const char* begin, const char* end) : begin_(begin), end_(end) {}
+  explicit StringView(const char* str) : begin_(str), end_(begin_ + std::char_traits<char>::length(str)) {}
+  explicit StringView(const std::string& str): begin_(&*str.begin()), end_(begin_ + str.length()) {}
+
+  constexpr const char* begin() const noexcept {
+    return begin_;
+  }
+
+  constexpr const char* end() const noexcept {
+    return end_;
+  }
+
+  constexpr bool empty() const noexcept {
+    return begin_ == end_;
+  }
+
+  std::reverse_iterator<const char*> rbegin() const noexcept {

Review comment:
       I find it a bit confusing that `begin`/`end` are pointers, but `rbegin`/`rend` are iterators.  Could both be iterators?

##########
File path: encrypt-config/ArgParser.cpp
##########
@@ -0,0 +1,192 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 <string>
+#include <set>
+#include <iostream>
+#include <algorithm>
+#include "ArgParser.h"
+#include "utils/OptionalUtils.h"
+#include "utils/StringUtils.h"
+
+namespace org {
+namespace apache {
+namespace nifi {
+namespace minifi {
+namespace encrypt_config {
+
+const std::vector<Argument> Arguments::simple_arguments_{
+    {std::vector<std::string>{"--minifi-home", "-m"},
+     true,
+     "minifi home",
+     "Specifies the home directory used by the minifi agent"}
+};
+
+const std::vector<FlagArgument> Arguments::flag_arguments_{
+    {std::vector<std::string>{"--help", "-h"},
+     "Prints this help message"},
+    {std::vector<std::string>{"--encrypt-flow-config"},
+     "If set, the flow configuration file (as specified in minifi.properties) is also encrypted."}
+};
+
+std::string Arguments::getHelp() {
+  std::stringstream ss;
+  ss << "Usage: " << "encrypt-config";
+  for (const auto& simple_arg : simple_arguments_) {
+    ss << " ";
+    if (!simple_arg.required) {
+      ss << "[";
+    }
+    ss << utils::StringUtils::join("|", simple_arg.names)
+        << " <" << simple_arg.value_name << ">";
+    if (!simple_arg.required) {
+      ss << "]";
+    }
+  }
+  for (const auto& flag : flag_arguments_) {
+    ss << " [" << utils::StringUtils::join("|", flag.names) << "]";
+  }
+  ss << std::endl;
+  for (const auto& simple_arg : simple_arguments_) {
+    ss << "\t";
+    ss << utils::StringUtils::join("|", simple_arg.names) << " : ";
+    if (simple_arg.required) {
+      ss << "(required)";
+    } else {
+      ss << "(optional)";
+    }
+    ss << " " << simple_arg.description;
+    ss << std::endl;
+  }
+  for (const auto& flag : flag_arguments_) {
+    ss << "\t" << utils::StringUtils::join("|", flag.names) << " : "
+        << flag.description << std::endl;
+  }
+  return ss.str();
+}
+
+void Arguments::set(const std::string& key, const std::string& value) {
+  if (get(key)) {
+    std::cerr << "Key is specified more that once \"" << key << "\"" << std::endl;
+    std::cerr << getHelp();
+    std::exit(1);
+  }
+  simple_args_[key] = value;
+}
+
+void Arguments::set(const std::string& flag) {
+  if (isSet(flag)) {
+    std::cerr << "Flag is specified more that once \"" << flag << "\"" << std::endl;
+    std::cerr << getHelp();
+    std::exit(1);
+  }
+  flag_args_.insert(flag);
+}
+
+utils::optional<std::string> Arguments::get(const std::string &key) const {
+  utils::optional<Argument> opt_arg = getSimpleArg(key);
+  if (!opt_arg) {
+    return {};
+  }
+  for (const auto& name : opt_arg->names) {
+    auto it = simple_args_.find(name);
+    if (it != simple_args_.end()) {
+      return it->second;
+    }
+  }
+  return {};
+}
+
+bool Arguments::isSet(const std::string &flag) const {
+  utils::optional<FlagArgument> opt_flag = getFlag(flag);
+  if (!opt_flag) {
+    return false;
+  }
+  return std::any_of(opt_flag->names.begin(), opt_flag->names.end(), [&] (const std::string& name) {
+    return flag_args_.find(name) != flag_args_.end();
+  });
+}
+
+Arguments Arguments::parse(int argc, char* argv[]) {
+  Arguments args;
+  for (int argIdx = 1; argIdx < argc; ++argIdx) {
+    std::string key{argv[argIdx]};
+    if (getFlag(key)) {
+      args.set(key);
+      continue;
+    }
+    if (!getSimpleArg(key)) {
+      std::cerr << "Unrecognized option: \"" << key << "\"" << std::endl;
+      std::cerr << getHelp();
+      std::exit(1);
+    }
+    if (argIdx == argc - 1) {
+      std::cerr << "No value specified for key \"" << key << "\"" << std::endl;
+      std::cerr << getHelp();
+      std::exit(1);
+    }
+    ++argIdx;
+    std::string value{argv[argIdx]};
+    args.set(key, value);
+  }
+  if (args.isSet("-h")) {
+    std::cout << getHelp();
+    std::exit(0);
+  }
+  for (const auto& simple_arg : simple_arguments_) {
+    if (simple_arg.required) {
+      bool found = false;
+      for (const auto& name : simple_arg.names) {
+        if (args.get(name)) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        std::cerr << "Missing required option [" << utils::StringUtils::join("|", simple_arg.names) << "]" << std::endl;

Review comment:
       since the option is required, I don't think its name should be between `[ ... ]`s

##########
File path: encrypt-config/EncryptConfig.cpp
##########
@@ -42,40 +42,72 @@ namespace nifi {
 namespace minifi {
 namespace encrypt_config {
 
-EncryptConfig::EncryptConfig(int argc, char* argv[]) : minifi_home_(parseMinifiHomeFromTheOptions(argc, argv)) {
+EncryptConfig::EncryptConfig(const std::string& minifi_home) : minifi_home_(minifi_home) {
   if (sodium_init() < 0) {
     throw std::runtime_error{"Could not initialize the libsodium library!"};
   }
+  keys_ = getEncryptionKeys();
 }
 
-std::string EncryptConfig::parseMinifiHomeFromTheOptions(int argc, char* argv[]) {
-  if (argc >= 2) {
-    for (int i = 1; i < argc; ++i) {
-      std::string argstr(argv[i]);
-      if ((argstr == "-h") || (argstr == "--help")) {
-        std::cout << USAGE_STRING << std::endl;
-        std::exit(0);
-      }
-    }
+EncryptConfig::EncryptionType EncryptConfig::encryptSensitiveProperties() const {
+  encryptSensitiveProperties(keys_);
+  if (keys_.decryption_key) {
+    return EncryptionType::RE_ENCRYPT;
   }
+  return EncryptionType::ENCRYPT;
+}
 
-  if (argc >= 3) {
-    for (int i = 1; i < argc; ++i) {
-      std::string argstr(argv[i]);
-      if ((argstr == "-m") || (argstr == "--minifi-home")) {
-        if (i+1 < argc) {
-          return std::string(argv[i+1]);
-        }
-      }
-    }
+void EncryptConfig::encryptFlowConfig() const {
+  encrypt_config::ConfigFile properties_file{std::ifstream{propertiesFilePath()}};
+  utils::optional<std::string> config_path = properties_file.getValue(Configure::nifi_flow_configuration_file);
+  if (!config_path) {
+    config_path = utils::file::PathUtils::resolve(minifi_home_, "conf/config.yml");
+    std::cout << "Couldn't find path of configuration file, using default: \"" << *config_path << "\"\n";
+  } else {
+    config_path = utils::file::PathUtils::resolve(minifi_home_, *config_path);
+    std::cout << "Encrypting flow configuration file: \"" << *config_path << "\"\n";
+  }
+  std::string config_content;
+  try {
+    std::ifstream config_file{*config_path, std::ios::binary};
+    config_file.exceptions(std::ios::failbit | std::ios::badbit);
+    config_content = std::string{std::istreambuf_iterator<char>(config_file), {}};
+  } catch (...) {
+    std::cerr << "Error while reading flow configuration file \"" << *config_path << "\"\n";
+    throw;
   }
+  try {
+    utils::crypto::decrypt(config_content, keys_.encryption_key);
+    std::cout << "Flow config file is already properly encrypted.\n";
+    return;
+  } catch (const std::exception&) {}
 
-  throw std::runtime_error{USAGE_STRING};
-}
+  if (utils::crypto::isEncrypted(config_content)) {
+    if (!keys_.decryption_key) {
+      std::cerr << "Config file is encrypted, but no deprecated key is set.\n";
+      std::exit(1);
+    }
+    std::cout << "Trying to decrypt flow config file using the deprecated key ...\n";
+    try {
+      config_content = utils::crypto::decrypt(config_content, *keys_.decryption_key);
+    } catch (const std::exception&) {
+      std::cerr << "Flow config is encrypted, but couldn't be decrypted.\n";
+      std::exit(1);
+    }
+  } else {
+    std::cout << "Flow config file is not encrypted, using as-is.\n";

Review comment:
       I don't think this log is necessary, as this is the default behavior.

##########
File path: extensions/http-curl/tests/C2ConfigEncryption.cpp
##########
@@ -0,0 +1,56 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#undef NDEBUG
+#include <iterator>
+#include <fstream>
+#include "HTTPIntegrationBase.h"
+#include "HTTPHandlers.h"
+#include "utils/IntegrationTestUtils.h"
+#include "utils/EncryptionProvider.h"
+
+int main(int argc, char **argv) {
+  const cmd_args args = parse_cmdline_args(argc, argv, "update");
+  TestController controller;
+  // copy config file to temporary location as it will get overridden
+  char tmp_format[] = "/var/tmp/c2.XXXXXX";
+  std::string home_path = controller.createTempDirectory(tmp_format);
+  std::string config_file = utils::file::FileUtils::concat_path(home_path, "config.yml");
+  utils::file::FileUtils::copy_file(args.test_file, config_file);
+  C2UpdateHandler handler(args.test_file);

Review comment:
       how does this work?  line 49 suggests `C2UpdateHandler` will encrypt the copied `config_file`, but it is constructed with the original `args.test_file`




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org