Merge pull request #6500 from ajaffie/feature/cli-ca-remove-6049

Implemented `ca remove` cli command + documentation.
This commit is contained in:
Michael Friedrich 2019-06-07 11:30:42 +02:00 committed by GitHub
commit ee4c5c5c2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 327 additions and 9 deletions

View File

@ -451,6 +451,16 @@ information/cli: Signed certificate for 'CN = icinga2-client2.localdomain'.
> `ca list` cannot be used as historical inventory. Certificate
> signing requests older than 1 week are automatically deleted.
You can also remove an undesired CSR using the `ca remove` command using the
syntax as the `ca sign` command.
```
[root@pym ~]# icinga2 ca remove 5c31ca0e2269c10363a97e40e3f2b2cd56493f9194d5b1852541b835970da46e
information/cli: Certificate 5c31ca0e2269c10363a97e40e3f2b2cd56493f9194d5b1852541b835970da46e removed.
```
If you want to restore a certificate you have removed, you can use `ca restore`.
## Client/Satellite Setup <a id="distributed-monitoring-setup-satellite-client"></a>
This section describes the setup of a satellite and/or client connected to an

View File

@ -21,6 +21,8 @@ Usage:
Supported commands:
* api setup (setup for API)
* ca list (lists all certificate signing requests)
* ca restore (restores a removed certificate request)
* ca remove (removes an outstanding certificate request)
* ca sign (signs an outstanding certificate request)
* console (Icinga debug console)
* daemon (starts Icinga 2)
@ -185,6 +187,8 @@ Usage:
Supported commands:
* ca list (lists all certificate signing requests)
* ca sign (signs an outstanding certificate request)
* ca restore (restores a removed certificate request)
* ca remove (removes an outstanding certificate request)
Global options:
-h [ --help ] show this help message
@ -232,6 +236,7 @@ Command options:
--all List all certificate signing requests, including
signed. Note: Old requests are automatically
cleaned by Icinga after 1 week.
--removed List all removed CSRs (for use with 'ca restore')
--json encode output as JSON
Report bugs at <https://github.com/Icinga/icinga2>

View File

@ -164,6 +164,16 @@ or with sudo.
You can use the new `--all` parameter to show all signing requests.
Note that Icinga automatically purges signed requests older than 1 week.
#### New: CA Remove/Restore <a id="upgrading-to-2-11-cli-commands-ca-remove-restore></a>
`ca remove` allows you to remove pending signing requests. Once the
master receives a CSR, and it is marked as removed, the request is
denied.
`ca restore` allows you to restore a removed signing request. You
can list removed signing requests with the new `--removed` parameter
for `ca list`.
### Configuration <a id="upgrading-to-2-11-configuration"></a>
The deprecated `concurrent_checks` attribute in the [checker feature](09-object-types.md#objecttype-checkercomponent)

View File

@ -5,6 +5,8 @@ set(cli_SOURCES
apisetupcommand.cpp apisetupcommand.hpp
apisetuputility.cpp apisetuputility.hpp
calistcommand.cpp calistcommand.hpp
caremovecommand.cpp caremovecommand.hpp
carestorecommand.cpp carestorecommand.hpp
casigncommand.cpp casigncommand.hpp
clicommand.cpp clicommand.hpp
consolecommand.cpp consolecommand.hpp

View File

@ -14,32 +14,49 @@ namespace po = boost::program_options;
REGISTER_CLICOMMAND("ca/list", CAListCommand);
/**
* Provide a long CLI description sentence.
*
* @return text
*/
String CAListCommand::GetDescription() const
{
return "Lists pending certificate signing requests.";
}
/**
* Provide a short CLI description.
*
* @return text
*/
String CAListCommand::GetShortDescription() const
{
return "lists pending certificate signing requests";
}
/**
* Initialize available CLI parameters.
*
* @param visibleDesc Register visible parameters.
* @param hiddenDesc Register hidden parameters.
*/
void CAListCommand::InitParameters(boost::program_options::options_description& visibleDesc,
boost::program_options::options_description& hiddenDesc) const
{
visibleDesc.add_options()
("all", "List all certificate signing requests, including signed. Note: Old requests are automatically cleaned by Icinga after 1 week.")
("removed", "List all removed CSRs (for use with 'ca restore')")
("json", "encode output as JSON");
}
/**
* The entry point for the "ca list" CLI command.
*
* @returns An exit status.
* @return An exit status.
*/
int CAListCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
{
Dictionary::Ptr requests = PkiUtility::GetCertificateRequests();
Dictionary::Ptr requests = PkiUtility::GetCertificateRequests(vm.count("removed"));
if (vm.count("json"))
std::cout << JsonEncode(requests);

View File

@ -0,0 +1,93 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "cli/caremovecommand.hpp"
#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/tlsutility.hpp"
#include "remote/apilistener.hpp"
using namespace icinga;
REGISTER_CLICOMMAND("ca/remove", CARemoveCommand);
/**
* Provide a long CLI description sentence.
*
* @return text
*/
String CARemoveCommand::GetDescription() const
{
return "Removes an outstanding certificate request.";
}
/**
* Provide a short CLI description.
*
* @return text
*/
String CARemoveCommand::GetShortDescription() const
{
return "removes an outstanding certificate request";
}
/**
* Define minimum arguments without key parameter.
*
* @return number of arguments
*/
int CARemoveCommand::GetMinArguments() const
{
return 1;
}
/**
* Impersonate as Icinga user.
*
* @return impersonate level
*/
ImpersonationLevel CARemoveCommand::GetImpersonationLevel() const
{
return ImpersonateIcinga;
}
/**
* The entry point for the "ca remove" CLI command.
*
* @returns An exit status.
*/
int CARemoveCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
{
String fingerPrint = ap[0];
String requestFile = ApiListener::GetCertificateRequestsDir() + "/" + fingerPrint + ".json";
if (!Utility::PathExists(requestFile)) {
Log(LogCritical, "cli")
<< "No request exists for fingerprint '" << fingerPrint << "'.";
return 1;
}
Dictionary::Ptr request = Utility::LoadJsonFile(requestFile);
std::shared_ptr<X509> certRequest = StringToCertificate(request->Get("cert_request"));
if (!certRequest) {
Log(LogCritical, "cli", "Certificate request is invalid. Could not parse X.509 certificate for the 'cert_request' attribute.");
return 1;
}
String cn = GetCertificateCN(certRequest);
if (request->Contains("cert_response")) {
Log(LogCritical, "cli")
<< "Certificate request for CN '" << cn << "' already signed, removal is not possible.";
return 1;
}
Utility::SaveJsonFile(ApiListener::GetCertificateRequestsDir() + "/" + fingerPrint + ".removed", 0600, request);
Utility::Remove(requestFile);
Log(LogInformation, "cli")
<< "Certificate request for CN " << cn << " removed.";
return 0;
}

View File

@ -0,0 +1,30 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#ifndef CAREMOVECOMMAND_H
#define CAREMOVECOMMAND_H
#include "cli/clicommand.hpp"
namespace icinga
{
/**
* The "ca remove" command.
*
* @ingroup cli
*/
class CARemoveCommand final : public CLICommand
{
public:
DECLARE_PTR_TYPEDEFS(CARemoveCommand);
String GetDescription() const override;
String GetShortDescription() const override;
int GetMinArguments() const override;
ImpersonationLevel GetImpersonationLevel() const override;
int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
};
}
#endif /* CAREMOVECOMMAND_H */

View File

@ -0,0 +1,88 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "cli/carestorecommand.hpp"
#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/tlsutility.hpp"
#include "remote/apilistener.hpp"
using namespace icinga;
REGISTER_CLICOMMAND("ca/restore", CARestoreCommand);
/**
* Provide a long CLI description sentence.
*
* @return text
*/
String CARestoreCommand::GetDescription() const
{
return "Restores a previously removed certificate request.";
}
/**
* Provide a short CLI description.
*
* @return text
*/
String CARestoreCommand::GetShortDescription() const
{
return "restores a removed certificate request";
}
/**
* Define minimum arguments without key parameter.
*
* @return number of arguments
*/
int CARestoreCommand::GetMinArguments() const
{
return 1;
}
/**
* Impersonate as Icinga user.
*
* @return impersonate level
*/
ImpersonationLevel CARestoreCommand::GetImpersonationLevel() const
{
return ImpersonateIcinga;
}
/**
* The entry point for the "ca restore" CLI command.
*
* @returns An exit status.
*/
int CARestoreCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
{
String fingerPrint = ap[0];
String removedRequestFile = ApiListener::GetCertificateRequestsDir() + "/" + fingerPrint + ".removed";
if (!Utility::PathExists(removedRequestFile)) {
Log(LogCritical, "cli")
<< "Cannot find removed fingerprint '" << fingerPrint << "', bailing out.";
return 1;
}
Dictionary::Ptr request = Utility::LoadJsonFile(removedRequestFile);
std::shared_ptr<X509> certRequest = StringToCertificate(request->Get("cert_request"));
if (!certRequest) {
Log(LogCritical, "cli", "Certificate request is invalid. Could not parse X.509 certificate for the 'cert_request' attribute.");
/* Purge the file when we know that it is broken. */
Utility::Remove(removedRequestFile);
return 1;
}
Utility::SaveJsonFile(ApiListener::GetCertificateRequestsDir() + "/" + fingerPrint + ".json", 0600, request);
Utility::Remove(removedRequestFile);
Log(LogInformation, "cli")
<< "Restored certificate request for CN '" << GetCertificateCN(certRequest) << "', sign it with:\n"
<< "\"icinga2 ca sign " << fingerPrint << "\"";
return 0;
}

View File

@ -0,0 +1,30 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#ifndef CARESTORECOMMAND_H
#define CARESTORECOMMAND_H
#include "cli/clicommand.hpp"
namespace icinga
{
/**
* The "ca restore" command.
*
* @ingroup cli
*/
class CARestoreCommand final : public CLICommand
{
public:
DECLARE_PTR_TYPEDEFS(CARestoreCommand);
String GetDescription() const override;
String GetShortDescription() const override;
int GetMinArguments() const override;
ImpersonationLevel GetImpersonationLevel() const override;
int Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const override;
};
}
#endif /* CASTORECOMMAND_H */

View File

@ -1,30 +1,50 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "cli/casigncommand.hpp"
#include "remote/apilistener.hpp"
#include "base/logger.hpp"
#include "base/application.hpp"
#include "base/tlsutility.hpp"
#include "remote/apilistener.hpp"
using namespace icinga;
REGISTER_CLICOMMAND("ca/sign", CASignCommand);
/**
* Provide a long CLI description sentence.
*
* @return text
*/
String CASignCommand::GetDescription() const
{
return "Signs an outstanding certificate request.";
}
/**
* Provide a short CLI description.
*
* @return text
*/
String CASignCommand::GetShortDescription() const
{
return "signs an outstanding certificate request";
}
/**
* Define minimum arguments without key parameter.
*
* @return number of arguments
*/
int CASignCommand::GetMinArguments() const
{
return 1;
}
/**
* Impersonate as Icinga user.
*
* @return impersonate level
*/
ImpersonationLevel CASignCommand::GetImpersonationLevel() const
{
return ImpersonateIcinga;
@ -33,7 +53,7 @@ ImpersonationLevel CASignCommand::GetImpersonationLevel() const
/**
* The entry point for the "ca sign" CLI command.
*
* @returns An exit status.
* @return An exit status.
*/
int CASignCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
{

View File

@ -129,6 +129,12 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
return result;
}
} else if (Utility::PathExists(requestDir + "/" + certFingerprint + ".removed")) {
Log(LogInformation, "JsonRpcConnection")
<< "Certificate for CN " << cn << " has been removed. Ignoring signing request.";
result->Set("status_code", 1);
result->Set("error", "Ticket for CN " + cn + " declined by administrator.");
return result;
}
std::shared_ptr<X509> newcert;

View File

@ -18,6 +18,7 @@
#include <fstream>
#include <iostream>
#include <boost/asio/ssl/context.hpp>
#include <boost/filesystem/path.hpp>
using namespace icinga;
@ -368,8 +369,9 @@ static void CollectRequestHandler(const Dictionary::Ptr& requests, const String&
Dictionary::Ptr result = new Dictionary();
String fingerprint = Utility::BaseName(requestFile);
fingerprint = fingerprint.SubStr(0, fingerprint.GetLength() - 5);
namespace fs = boost::filesystem;
fs::path file(requestFile.Begin(), requestFile.End());
String fingerprint = file.stem().string();
String certRequestText = request->Get("cert_request");
result->Set("cert_request", certRequestText);
@ -414,14 +416,19 @@ static void CollectRequestHandler(const Dictionary::Ptr& requests, const String&
requests->Set(fingerprint, result);
}
Dictionary::Ptr PkiUtility::GetCertificateRequests()
Dictionary::Ptr PkiUtility::GetCertificateRequests(bool removed)
{
Dictionary::Ptr requests = new Dictionary();
String requestDir = ApiListener::GetCertificateRequestsDir();
String ext = "json";
if (removed)
ext = "removed";
if (Utility::PathExists(requestDir))
Utility::Glob(requestDir + "/*.json", std::bind(&CollectRequestHandler, requests, _1), GlobFile);
Utility::Glob(requestDir + "/*." + ext, std::bind(&CollectRequestHandler, requests, _1), GlobFile);
return requests;
}

View File

@ -29,7 +29,7 @@ public:
const String& certfile, const String& cafile, const std::shared_ptr<X509>& trustedcert,
const String& ticket = String());
static String GetCertificateInformation(const std::shared_ptr<X509>& certificate);
static Dictionary::Ptr GetCertificateRequests();
static Dictionary::Ptr GetCertificateRequests(bool removed = false);
private:
PkiUtility();