command-authenticator.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2014-2024, Regents of the University of California,
4  * Arizona Board of Regents,
5  * Colorado State University,
6  * University Pierre & Marie Curie, Sorbonne University,
7  * Washington University in St. Louis,
8  * Beijing Institute of Technology,
9  * The University of Memphis.
10  *
11  * This file is part of NFD (Named Data Networking Forwarding Daemon).
12  * See AUTHORS.md for complete list of NFD authors and contributors.
13  *
14  * NFD is free software: you can redistribute it and/or modify it under the terms
15  * of the GNU General Public License as published by the Free Software Foundation,
16  * either version 3 of the License, or (at your option) any later version.
17  *
18  * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
19  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
20  * PURPOSE. See the GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
24  */
25 
27 #include "common/logger.hpp"
28 
29 #include <ndn-cxx/security/certificate-fetcher-offline.hpp>
30 #include <ndn-cxx/security/certificate-request.hpp>
31 #include <ndn-cxx/security/validation-policy.hpp>
32 #include <ndn-cxx/security/validation-policy-accept-all.hpp>
33 #include <ndn-cxx/security/validation-policy-command-interest.hpp>
34 #include <ndn-cxx/tag.hpp>
35 #include <ndn-cxx/util/io.hpp>
36 
37 #include <boost/filesystem/operations.hpp>
38 #include <boost/filesystem/path.hpp>
39 
40 namespace security = ndn::security;
41 
42 namespace nfd {
43 
44 NFD_LOG_INIT(CommandAuthenticator);
45 // INFO: configuration change, etc
46 // DEBUG: per authentication request result
47 
51 using SignerTag = ndn::SimpleTag<Name, 20>;
52 
56 static std::optional<std::string>
57 getSignerFromTag(const Interest& interest)
58 {
59  auto signerTag = interest.getTag<SignerTag>();
60  if (signerTag == nullptr) {
61  return std::nullopt;
62  }
63  else {
64  return signerTag->get().toUri();
65  }
66 }
67 
71 class CommandAuthenticatorValidationPolicy final : public security::ValidationPolicy
72 {
73 public:
74  void
75  checkPolicy(const Interest& interest, const shared_ptr<security::ValidationState>& state,
76  const ValidationContinuation& continueValidation) final
77  {
78  auto sigInfo = getSignatureInfo(interest, *state);
79  if (!state->getOutcome()) { // already failed
80  return;
81  }
82  Name klName = getKeyLocatorName(sigInfo, *state);
83  if (!state->getOutcome()) { // already failed
84  return;
85  }
86 
87  // SignerTag must be placed on the 'original Interest' in ValidationState to be available for
88  // InterestValidationSuccessCallback. The 'interest' parameter refers to a different instance
89  // which is copied into 'original Interest'.
90  auto state1 = std::dynamic_pointer_cast<security::InterestValidationState>(state);
91  state1->getOriginalInterest().setTag(make_shared<SignerTag>(klName));
92 
93  continueValidation(make_shared<security::CertificateRequest>(klName), state);
94  }
95 
96  void
97  checkPolicy(const Data&, const shared_ptr<security::ValidationState>&,
98  const ValidationContinuation&) final
99  {
100  // Non-certificate Data are not handled by CommandAuthenticator.
101  // Non-anchor certificates cannot be retrieved by offline fetcher.
102  BOOST_ASSERT_MSG(false, "Data should not be passed to this policy");
103  }
104 };
105 
106 shared_ptr<CommandAuthenticator>
108 {
109  return shared_ptr<CommandAuthenticator>(new CommandAuthenticator);
110 }
111 
112 CommandAuthenticator::CommandAuthenticator() = default;
113 
114 void
116 {
117  configFile.addSectionHandler("authorizations", [this] (auto&&... args) {
118  processConfig(std::forward<decltype(args)>(args)...);
119  });
120 }
121 
122 void
123 CommandAuthenticator::processConfig(const ConfigSection& section, bool isDryRun, const std::string& filename)
124 {
125  if (!isDryRun) {
126  NFD_LOG_DEBUG("resetting authorizations");
127  for (auto& kv : m_validators) {
128  kv.second = make_shared<security::Validator>(
129  make_unique<security::ValidationPolicyCommandInterest>(make_unique<CommandAuthenticatorValidationPolicy>()),
130  make_unique<security::CertificateFetcherOffline>());
131  }
132  }
133 
134  if (section.empty()) {
135  NDN_THROW(ConfigFile::Error("'authorize' is missing under 'authorizations'"));
136  }
137 
138  int authSectionIndex = 0;
139  for (const auto& [sectionName, authSection] : section) {
140  if (sectionName != "authorize") {
141  NDN_THROW(ConfigFile::Error("'" + sectionName + "' section is not permitted under 'authorizations'"));
142  }
143 
144  std::string certfile;
145  try {
146  certfile = authSection.get<std::string>("certfile");
147  }
148  catch (const boost::property_tree::ptree_error&) {
149  NDN_THROW(ConfigFile::Error("'certfile' is missing under authorize[" +
150  std::to_string(authSectionIndex) + "]"));
151  }
152 
153  bool isAny = false;
154  shared_ptr<security::Certificate> cert;
155  if (certfile == "any") {
156  isAny = true;
157  NFD_LOG_WARN("'certfile any' is intended for demo purposes only and "
158  "SHOULD NOT be used in production environments");
159  }
160  else {
161  using namespace boost::filesystem;
162  path certfilePath = absolute(certfile, path(filename).parent_path());
163  cert = ndn::io::load<security::Certificate>(certfilePath.string());
164  if (cert == nullptr) {
165  NDN_THROW(ConfigFile::Error("cannot load certfile " + certfilePath.string() +
166  " for authorize[" + std::to_string(authSectionIndex) + "]"));
167  }
168  }
169 
170  const ConfigSection* privSection = nullptr;
171  try {
172  privSection = &authSection.get_child("privileges");
173  }
174  catch (const boost::property_tree::ptree_error&) {
175  NDN_THROW(ConfigFile::Error("'privileges' is missing under authorize[" +
176  std::to_string(authSectionIndex) + "]"));
177  }
178 
179  if (privSection->empty()) {
180  NFD_LOG_WARN("No privileges granted to certificate " << certfile);
181  }
182  for (const auto& kv : *privSection) {
183  const std::string& module = kv.first;
184  auto found = m_validators.find(module);
185  if (found == m_validators.end()) {
186  NDN_THROW(ConfigFile::Error("unknown module '" + module +
187  "' under authorize[" + std::to_string(authSectionIndex) + "]"));
188  }
189 
190  if (isDryRun) {
191  continue;
192  }
193 
194  if (isAny) {
195  found->second = make_shared<security::Validator>(make_unique<security::ValidationPolicyAcceptAll>(),
196  make_unique<security::CertificateFetcherOffline>());
197  NFD_LOG_INFO("authorize module=" << module << " signer=any");
198  }
199  else {
200  const Name& keyName = cert->getKeyName();
201  security::Certificate certCopy = *cert;
202  found->second->loadAnchor(certfile, std::move(certCopy));
203  NFD_LOG_INFO("authorize module=" << module << " signer=" << keyName << " certfile=" << certfile);
204  }
205  }
206 
207  ++authSectionIndex;
208  }
209 }
210 
211 ndn::mgmt::Authorization
212 CommandAuthenticator::makeAuthorization(const std::string& module, const std::string& verb)
213 {
214  m_validators[module]; // declares module, so that privilege is recognized
215 
216  return [module, self = shared_from_this()] (const Name&, const Interest& interest,
217  const ndn::mgmt::ControlParameters*,
218  const ndn::mgmt::AcceptContinuation& accept,
219  const ndn::mgmt::RejectContinuation& reject) {
220  auto validator = self->m_validators.at(module);
221 
222  auto successCb = [accept, validator] (const Interest& interest1) {
223  auto signer1 = getSignerFromTag(interest1);
224  BOOST_ASSERT(signer1 || // signer must be available unless 'certfile any'
225  dynamic_cast<security::ValidationPolicyAcceptAll*>(&validator->getPolicy()) != nullptr);
226  std::string signer = signer1.value_or("*");
227  NFD_LOG_DEBUG("accept " << interest1.getName() << " signer=" << signer);
228  accept(signer);
229  };
230 
231  using ndn::security::ValidationError;
232  auto failureCb = [reject] (const Interest& interest1, const ValidationError& err) {
233  auto reply = ndn::mgmt::RejectReply::STATUS403;
234  if (err.getCode() == ValidationError::MALFORMED_SIGNATURE ||
235  err.getCode() == ValidationError::INVALID_KEY_LOCATOR) {
236  // do not waste cycles signing and sending a reply if the command is clearly malformed
237  reply = ndn::mgmt::RejectReply::SILENT;
238  }
239  NFD_LOG_DEBUG("reject " << interest1.getName() << " signer=" <<
240  getSignerFromTag(interest1).value_or("?") << " reason=" << err);
241  reject(reply);
242  };
243 
244  if (validator) {
245  validator->validate(interest, successCb, failureCb);
246  }
247  else {
248  NFD_LOG_DEBUG("reject " << interest.getName() << " signer=" <<
249  getSignerFromTag(interest).value_or("?") << " reason=Unauthorized");
250  reject(ndn::mgmt::RejectReply::STATUS403);
251  }
252  };
253 }
254 
255 } // namespace nfd
Provides ControlCommand authorization according to NFD's configuration file.
ndn::mgmt::Authorization makeAuthorization(const std::string &module, const std::string &verb)
Returns an Authorization function for module/verb command.
void setConfigFile(ConfigFile &configFile)
static shared_ptr< CommandAuthenticator > create()
Configuration file parsing utility.
Definition: config-file.hpp:66
void addSectionHandler(const std::string &sectionName, ConfigSectionHandler subscriber)
Setup notification of configuration file sections.
Definition: config-file.cpp:77
#define NFD_LOG_INFO
Definition: logger.hpp:39
#define NFD_LOG_INIT(name)
Definition: logger.hpp:31
#define NFD_LOG_WARN
Definition: logger.hpp:40
#define NFD_LOG_DEBUG
Definition: logger.hpp:38
-status-http-server
Definition: common.hpp:71
ndn::SimpleTag< Name, 20 > SignerTag
An Interest tag to store the command signer.
boost::property_tree::ptree ConfigSection
A configuration file section.
Definition: config-file.hpp:41
static std::optional< std::string > getSignerFromTag(const Interest &interest)
Obtain signer from a SignerTag attached to interest, if available.