aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/clickhouse/src/Server/CertificateReloader.cpp
blob: 8795d4807de1d9e1b8c9cd996af7c1254170e897 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "CertificateReloader.h"

#if USE_SSL

#include <Common/logger_useful.h>
#include <base/errnoToString.h>
#include <Poco/Net/Context.h>
#include <Poco/Net/SSLManager.h>
#include <Poco/Net/Utility.h>


namespace DB
{

namespace
{
/// Call set process for certificate.
int callSetCertificate(SSL * ssl, [[maybe_unused]] void * arg)
{
    return CertificateReloader::instance().setCertificate(ssl);
}

}


/// This is callback for OpenSSL. It will be called on every connection to obtain a certificate and private key.
int CertificateReloader::setCertificate(SSL * ssl)
{
    auto current = data.get();
    if (!current)
        return -1;

    SSL_use_certificate(ssl, const_cast<X509 *>(current->cert.certificate()));
    SSL_use_PrivateKey(ssl, const_cast<EVP_PKEY *>(static_cast<const EVP_PKEY *>(current->key)));

    int err = SSL_check_private_key(ssl);
    if (err != 1)
    {
        std::string msg = Poco::Net::Utility::getLastError();
        LOG_ERROR(log, "Unusable key-pair {}", msg);
        return -1;
    }

    return 1;
}


void CertificateReloader::init()
{
    LOG_DEBUG(log, "Initializing certificate reloader.");

    /// Set a callback for OpenSSL to allow get the updated cert and key.

    auto* ctx = Poco::Net::SSLManager::instance().defaultServerContext()->sslContext();
    SSL_CTX_set_cert_cb(ctx, callSetCertificate, nullptr);
    init_was_not_made = false;
}


void CertificateReloader::tryLoad(const Poco::Util::AbstractConfiguration & config)
{
    /// If at least one of the files is modified - recreate

    std::string new_cert_path = config.getString("openSSL.server.certificateFile", "");
    std::string new_key_path = config.getString("openSSL.server.privateKeyFile", "");

    /// For empty paths (that means, that user doesn't want to use certificates)
    /// no processing required

    if (new_cert_path.empty() || new_key_path.empty())
    {
        LOG_INFO(log, "One of paths is empty. Cannot apply new configuration for certificates. Fill all paths and try again.");
    }
    else
    {
        bool cert_file_changed = cert_file.changeIfModified(std::move(new_cert_path), log);
        bool key_file_changed = key_file.changeIfModified(std::move(new_key_path), log);
        std::string pass_phrase = config.getString("openSSL.server.privateKeyPassphraseHandler.options.password", "");

        if (cert_file_changed || key_file_changed)
        {
            LOG_DEBUG(log, "Reloading certificate ({}) and key ({}).", cert_file.path, key_file.path);
            data.set(std::make_unique<const Data>(cert_file.path, key_file.path, pass_phrase));
            LOG_INFO(log, "Reloaded certificate ({}) and key ({}).", cert_file.path, key_file.path);
        }

        /// If callback is not set yet
        try
        {
            if (init_was_not_made)
                init();
        }
        catch (...)
        {
            init_was_not_made = true;
            LOG_ERROR(log, getCurrentExceptionMessageAndPattern(/* with_stacktrace */ false));
        }
    }
}


CertificateReloader::Data::Data(std::string cert_path, std::string key_path, std::string pass_phrase)
    : cert(cert_path), key(/* public key */ "", /* private key */ key_path, pass_phrase)
{
}


bool CertificateReloader::File::changeIfModified(std::string new_path, Poco::Logger * logger)
{
    std::error_code ec;
    std::filesystem::file_time_type new_modification_time = std::filesystem::last_write_time(new_path, ec);
    if (ec)
    {
        LOG_ERROR(logger, "Cannot obtain modification time for {} file {}, skipping update. {}",
            description, new_path, errnoToString(ec.value()));
        return false;
    }

    if (new_path != path || new_modification_time != modification_time)
    {
        path = new_path;
        modification_time = new_modification_time;
        return true;
    }

    return false;
}

}

#endif