1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 Couchbase, Inc.
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17#include "config.h"
18
19#include <protocol/connection/client_connection.h>
20#include <protocol/connection/client_mcbp_commands.h>
21#include "memcached_audit_events.h"
22#include "testapp_environment.h"
23#include "utilities.h"
24
25#include <cJSON_utils.h>
26#include <platform/dirutils.h>
27#include <platform/make_unique.h>
28#include <platform/memorymap.h>
29#include <platform/strerror.h>
30#include <fstream>
31
32void TestBucketImpl::createEwbBucket(const std::string& name,
33                                     const std::string& plugin,
34                                     const std::string& config,
35                                     MemcachedConnection& conn) {
36    std::string cfg(plugin);
37    if (!config.empty()) {
38        cfg += ";" + config;
39    }
40    conn.createBucket(name, cfg, BucketType::EWouldBlock);
41}
42
43// Both memcache and ep-engine buckets support set_param for xattr on/off
44void TestBucketImpl::setXattrEnabled(MemcachedConnection& conn,
45                                     const std::string& bucketName,
46                                     bool value) {
47    conn.authenticate("@admin", "password", "PLAIN");
48    conn.selectBucket(bucketName);
49
50    // Encode a set_flush_param (like cbepctl)
51    BinprotGenericCommand cmd;
52    BinprotResponse resp;
53    cmd.setOp(PROTOCOL_BINARY_CMD_SET_PARAM);
54    cmd.setKey("xattr_enabled");
55    cmd.setExtrasValue<uint32_t>(htonl(protocol_binary_engine_param_flush));
56    if (value) {
57        cmd.setValue("true");
58    } else {
59        cmd.setValue("false");
60    }
61
62    conn.executeCommand(cmd, resp);
63    ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
64}
65
66void TestBucketImpl::setCompressionMode(MemcachedConnection& conn,
67                                        const std::string& bucketName,
68                                        const std::string value) {
69    conn.authenticate("@admin", "password", "PLAIN");
70    conn.selectBucket(bucketName);
71
72    // Encode a set_flush_param (like cbepctl)
73    BinprotGenericCommand cmd;
74    BinprotResponse resp;
75    cmd.setOp(PROTOCOL_BINARY_CMD_SET_PARAM);
76    cmd.setKey("compression_mode");
77    cmd.setExtrasValue<uint32_t>(htonl(protocol_binary_engine_param_flush));
78    cmd.setValue(value);
79
80    conn.executeCommand(cmd, resp);
81    ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
82}
83
84void TestBucketImpl::setMinCompressionRatio(MemcachedConnection& conn,
85                                            const std::string& bucketName,
86                                            const std::string value) {
87    conn.authenticate("@admin", "password", "PLAIN");
88    conn.selectBucket(bucketName);
89
90    // Encode a set_flush_param (like cbepctl)
91    BinprotGenericCommand cmd;
92    BinprotResponse resp;
93    cmd.setOp(PROTOCOL_BINARY_CMD_SET_PARAM);
94    cmd.setKey("min_compression_ratio");
95    cmd.setExtrasValue<uint32_t>(htonl(protocol_binary_engine_param_flush));
96    cmd.setValue(value);
97
98    conn.executeCommand(cmd, resp);
99    ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
100}
101
102class DefaultBucketImpl : public TestBucketImpl {
103public:
104    DefaultBucketImpl(std::string extraConfig = {})
105        : TestBucketImpl(extraConfig) {
106    }
107
108    void setUpBucket(const std::string& name,
109                     const std::string& config,
110                     MemcachedConnection& conn) override {
111        std::string settings = config;
112        if (!extraConfig.empty()) {
113            if (!settings.empty()) {
114                settings += ';';
115            }
116            settings += extraConfig;
117        }
118        createEwbBucket(name, "default_engine.so", settings, conn);
119    }
120
121    std::string getName() const override {
122        return "default_engine";
123    }
124
125    bool supportsOp(protocol_binary_command cmd) const override {
126        switch (cmd) {
127            case PROTOCOL_BINARY_CMD_DCP_OPEN:
128            case PROTOCOL_BINARY_CMD_DCP_ADD_STREAM:
129            case PROTOCOL_BINARY_CMD_DCP_CLOSE_STREAM:
130            case PROTOCOL_BINARY_CMD_DCP_STREAM_REQ:
131            case PROTOCOL_BINARY_CMD_DCP_GET_FAILOVER_LOG:
132            case PROTOCOL_BINARY_CMD_DCP_STREAM_END:
133            case PROTOCOL_BINARY_CMD_DCP_SNAPSHOT_MARKER:
134            case PROTOCOL_BINARY_CMD_DCP_MUTATION:
135            case PROTOCOL_BINARY_CMD_DCP_DELETION:
136            case PROTOCOL_BINARY_CMD_DCP_EXPIRATION:
137            case PROTOCOL_BINARY_CMD_DCP_FLUSH:
138            case PROTOCOL_BINARY_CMD_DCP_SET_VBUCKET_STATE:
139            case PROTOCOL_BINARY_CMD_DCP_NOOP:
140            case PROTOCOL_BINARY_CMD_DCP_BUFFER_ACKNOWLEDGEMENT:
141            case PROTOCOL_BINARY_CMD_DCP_CONTROL:
142            case PROTOCOL_BINARY_CMD_DCP_SYSTEM_EVENT:
143            case PROTOCOL_BINARY_CMD_SET_WITH_META:
144            case PROTOCOL_BINARY_CMD_SETQ_WITH_META:
145            case PROTOCOL_BINARY_CMD_ADD_WITH_META:
146            case PROTOCOL_BINARY_CMD_ADDQ_WITH_META:
147            case PROTOCOL_BINARY_CMD_DEL_WITH_META:
148            case PROTOCOL_BINARY_CMD_DELQ_WITH_META:
149            case PROTOCOL_BINARY_CMD_ENABLE_TRAFFIC:
150            case PROTOCOL_BINARY_CMD_DISABLE_TRAFFIC:
151            case PROTOCOL_BINARY_CMD_GET_FAILOVER_LOG:
152                return false;
153            default:
154                return true;
155        }
156    }
157
158    bool supportsPrivilegedBytes() const override {
159        return false;
160    }
161
162    size_t getMaximumDocSize() const override {
163        return 1024 * 1024;
164    }
165
166    bool supportsLastModifiedVattr() const override {
167        return false;
168    }
169
170    bool supportsPersistence() const override {
171        return false;
172    }
173};
174
175class EpBucketImpl : public TestBucketImpl {
176public:
177    EpBucketImpl(std::string extraConfig = {})
178        : TestBucketImpl(extraConfig),
179          dbPath("mc_testapp." + std::to_string(cb_getpid())) {
180        // Cleanup any files from a previous run still on disk.
181        try {
182            cb::io::rmrf(dbPath);
183        } catch (...) { /* nothing exists */
184        }
185    }
186
187    ~EpBucketImpl() {
188        // Cleanup any files created.
189        try {
190            cb::io::rmrf(dbPath);
191        } catch (...) { /* nothing exists */
192        }
193    }
194
195    void setUpBucket(const std::string& name,
196                     const std::string& config,
197                     MemcachedConnection& conn) override {
198        std::string settings = "dbname=" + dbPath + "/" + name;
199        if (!config.empty()) {
200            settings += ";" + config;
201        }
202        if (!extraConfig.empty()) {
203            settings += ";" + extraConfig;
204        }
205        createEwbBucket(name, "ep.so", settings, conn);
206
207        BinprotGenericCommand cmd;
208        BinprotResponse resp;
209
210        cmd.setOp(PROTOCOL_BINARY_CMD_SELECT_BUCKET);
211        cmd.setKey(name);
212        conn.executeCommand(cmd, resp);
213        ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
214
215        cmd.clear();
216        resp.clear();
217
218        cmd.setOp(PROTOCOL_BINARY_CMD_SET_VBUCKET);
219        cmd.setExtrasValue<uint32_t>(htonl(1));
220
221        conn.executeCommand(cmd, resp);
222        ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
223
224        do {
225            cmd.clear();
226            resp.clear();
227            cmd.setOp(PROTOCOL_BINARY_CMD_ENABLE_TRAFFIC);
228            // Enable traffic
229            conn.executeCommand(cmd, resp);
230        } while (resp.getStatus() == PROTOCOL_BINARY_RESPONSE_ETMPFAIL);
231
232        ASSERT_EQ(PROTOCOL_BINARY_RESPONSE_SUCCESS, resp.getStatus());
233    }
234
235    std::string getName() const override {
236        return "ep_engine";
237    }
238
239    bool supportsOp(protocol_binary_command cmd ) const override {
240        switch (cmd) {
241        case PROTOCOL_BINARY_CMD_FLUSH:
242        case PROTOCOL_BINARY_CMD_FLUSHQ:
243            // TODO: Flush *is* supported by ep-engine, but it needs traffic
244            // disabling before it's permitted.
245        case PROTOCOL_BINARY_CMD_SCRUB:
246            return false;
247
248        default:
249            return true;
250        }
251    }
252
253    bool supportsPrivilegedBytes() const override {
254        return true;
255    }
256
257    size_t getMaximumDocSize() const override {
258        return 20 * 1024 * 1024;
259    }
260
261    bool supportsLastModifiedVattr() const override {
262        return true;
263    }
264
265    bool supportsPersistence() const override {
266        return true;
267    }
268
269    /// Directory for any database files.
270    const std::string dbPath;
271};
272
273McdEnvironment::McdEnvironment(bool manageSSL_,
274                               std::string engineName,
275                               std::string engineConfig)
276    : manageSSL(manageSSL_) {
277    if (manageSSL) {
278        initialize_openssl();
279    }
280
281    if (engineName == "default") {
282        std::string config = "keep_deleted=true";
283        if (!engineConfig.empty()) {
284            config += ";" + engineConfig;
285        }
286        testBucket = std::make_unique<DefaultBucketImpl>(config);
287    } else if (engineName == "ep") {
288        testBucket = std::make_unique<EpBucketImpl>(engineConfig);
289    } else {
290        throw std::invalid_argument("Unknown engine '" + engineName +
291                                    "' "
292                                    "Options are 'default' and 'ep'");
293    }
294}
295
296McdEnvironment::~McdEnvironment() {
297   if (manageSSL) {
298       shutdown_openssl();
299   }
300}
301
302void McdEnvironment::SetUp() {
303    cwd = cb::io::getcwd();
304    SetupAuditFile();
305    SetupIsaslPw();
306    SetupRbacFile();
307}
308
309void McdEnvironment::SetupIsaslPw() {
310    isasl_file_name = SOURCE_ROOT;
311    isasl_file_name.append("/tests/testapp/cbsaslpw.json");
312    std::replace(isasl_file_name.begin(), isasl_file_name.end(), '\\', '/');
313
314    // Add the file to the exec environment
315    snprintf(isasl_env_var, sizeof(isasl_env_var), "CBSASL_PWFILE=%s",
316             isasl_file_name.c_str());
317    putenv(isasl_env_var);
318}
319
320void McdEnvironment::SetupAuditFile() {
321    try {
322        audit_file_name = cwd + "/" + cb::io::mktemp("audit.cfg");
323        audit_log_dir = cwd + "/" + cb::io::mktemp("audit.log");
324        const std::string descriptor = cwd + "/auditd";
325        EXPECT_NO_THROW(cb::io::rmrf(audit_log_dir));
326        cb::io::mkdirp(audit_log_dir);
327
328        // Generate the auditd config file.
329        audit_config.reset(cJSON_CreateObject());
330        cJSON_AddNumberToObject(audit_config.get(), "version", 2);
331        cJSON_AddStringToObject(audit_config.get(), "uuid", "this_is_the_uuid");
332        cJSON_AddFalseToObject(audit_config.get(), "auditd_enabled");
333        cJSON_AddNumberToObject(audit_config.get(), "rotate_interval", 1440);
334        cJSON_AddNumberToObject(audit_config.get(), "rotate_size", 20971520);
335        cJSON_AddFalseToObject(audit_config.get(), "buffered");
336        cJSON_AddStringToObject(audit_config.get(), "log_path",
337                                audit_log_dir.c_str());
338        cJSON_AddStringToObject(audit_config.get(), "descriptors_path",
339                                descriptor.c_str());
340        cJSON_AddItemToObject(audit_config.get(), "sync", cJSON_CreateArray());
341        cJSON_AddItemToObject(audit_config.get(), "disabled",
342                              cJSON_CreateArray());
343
344        auto* states = cJSON_CreateObject();
345        cJSON_AddStringToObject(
346                states,
347                std::to_string(MEMCACHED_AUDIT_AUTHENTICATION_SUCCEEDED)
348                        .c_str(),
349                "enabled");
350        cJSON_AddItemToObject(audit_config.get(), "event_states", states);
351        cJSON_AddFalseToObject(audit_config.get(), "filtering_enabled");
352        cJSON_AddItemToObject(
353                audit_config.get(), "disabled_userids", cJSON_CreateArray());
354
355    } catch (const std::exception& e) {
356        FAIL() << "Failed to generate audit configuration: " << e.what();
357    }
358
359    rewriteAuditConfig();
360}
361
362void McdEnvironment::TearDown() {
363    // Cleanup Audit config file
364    if (!audit_file_name.empty()) {
365        cb::io::rmrf(audit_file_name);
366    }
367
368    // Cleanup Audit log directory
369    if (!audit_log_dir.empty()) {
370        cb::io::rmrf(audit_log_dir);
371    }
372
373    // Cleanup RBAC configuration
374    if (!rbac_file_name.empty()) {
375        cb::io::rmrf(rbac_file_name);
376    }
377}
378
379void McdEnvironment::rewriteAuditConfig() {
380    try {
381        std::string audit_text = to_string(audit_config);
382        std::ofstream out(audit_file_name);
383        out.write(audit_text.c_str(), audit_text.size());
384        out.close();
385    } catch (std::exception& e) {
386        FAIL() << "Failed to store audit configuration: " << e.what();
387    }
388}
389
390void McdEnvironment::SetupRbacFile() {
391    std::string input_file{SOURCE_ROOT};
392    input_file.append("/tests/testapp/rbac.json");
393#ifdef WIN32
394    std::replace(input_file.begin(), input_file.end(), '\\', '/');
395#endif
396    cb::MemoryMappedFile map(input_file.c_str(),
397                             cb::MemoryMappedFile::Mode::RDONLY);
398    map.open();
399    std::string input(reinterpret_cast<char*>(map.getRoot()),
400                      map.getSize());
401    map.close();
402
403    rbac_data.reset(cJSON_Parse(input.c_str()));
404
405    rbac_file_name = cwd + "/" + cb::io::mktemp("rbac.json.XXXXXX");
406    rewriteRbacFile();
407}
408
409void McdEnvironment::rewriteRbacFile() {
410    try {
411        std::ofstream out(rbac_file_name);
412        out << to_string(rbac_data, true) << std::endl;
413        out.close();
414    } catch (std::exception& e) {
415        FAIL() << "Failed to store rbac configuration: " << e.what();
416    }
417}
418
419char McdEnvironment::isasl_env_var[256];
420