blob: fef8295c66246a9b80f9dc5161715d30570e18c6 [file] [log] [blame]
Václav Kubernátbabbab92021-01-27 09:25:05 +01001/*
2 * Copyright (C) 2021 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Václav Kubernát <kubernat@cesnet.cz>
5 *
6*/
7
8#include "trompeloeil_doctest.h"
9#include "fs-helpers/FileInjector.h"
10#include "fs-helpers/utils.h"
11#include "mock/system.h"
12#include "pretty_printers.h"
13#include "system/Authentication.h"
14#include "system_vars.h"
15#include "test_log_setup.h"
16#include "test_sysrepo_helpers.h"
17#include "tests/configure.cmake.h"
18#include "utils/io.h"
19#include "utils/libyang.h"
20
21using namespace std::string_literals;
22
23TEST_CASE("Authentication")
24{
25 FakeAuthentication mock;
26 TEST_INIT_LOGS;
27 auto srConn = std::make_shared<sysrepo::Connection>();
28 std::filesystem::path testDir = CMAKE_CURRENT_BINARY_DIR "/tests/authentication"s;
29 removeDirectoryTreeIfExists(testDir);
30 std::filesystem::create_directory(testDir);
31 std::filesystem::create_directory(testDir / "authorized_keys");
32 auto srSess = std::make_shared<sysrepo::Session>(srConn);
33 std::string authorized_keys_format = testDir / "authorized_keys/{USER}";
34 std::string etc_passwd = testDir / "etc_passwd";
35 std::string etc_shadow = testDir / "etc_shadow";
Tomáš Peckad9e741f2021-02-10 15:51:17 +010036 velia::system::Authentication auth(srSess, etc_passwd, etc_shadow, authorized_keys_format, [&mock] (const auto& user, const auto& password, const auto& etc_shadow) { mock.changePassword(user, password, etc_shadow); });
Václav Kubernátbabbab92021-01-27 09:25:05 +010037
38 auto test_srConn = std::make_shared<sysrepo::Connection>();
39 auto test_srSess = std::make_shared<sysrepo::Session>(test_srConn, SR_DS_OPERATIONAL);
40
41 FileInjector passwd(etc_passwd, std::filesystem::perms::owner_read,
42 "root:x:0:0::/root:/bin/bash\n"
43 "ci:x:1000:1000::/home/ci:/bin/bash\n"
44
45 );
46 FileInjector shadow(etc_shadow, std::filesystem::perms::owner_read,
47 "root::18514::::::\n"
48 "ci::20000::::::\n"
49 );
50
51 using velia::system::User;
52 SECTION("list users")
53 {
54 FileInjector rootKeys(testDir / "authorized_keys/root", std::filesystem::perms::owner_read,
55 "ssh-rsa SOME_KEY comment"
56 );
57 auto data = dataFromSysrepo(test_srSess, "/czechlight-system:authentication/users");
58 decltype(data) expected = {
59 {"[name='ci']/name", "ci"},
60 {"[name='ci']/password-last-change", "2024-10-04T00:00:00Z"},
61 {"[name='root']/name", "root"},
62 {"[name='root']/password-last-change", "2020-09-09T00:00:00Z"},
63 {"[name='root']/authorized-keys[index='0']", ""},
64 {"[name='root']/authorized-keys[index='0']/index", "0"},
65 {"[name='root']/authorized-keys[index='0']/public-key", "ssh-rsa SOME_KEY comment"}
66
67 };
68 REQUIRE(data == expected);
69 }
70
71 SECTION("RPCs/actions")
72 {
73 std::string rpcPath;
74 std::map<std::string, std::string> input;
75 std::map<std::string, std::string> expected;
76 std::unique_ptr<trompeloeil::expectation> expectation;
77
78 SECTION("change password")
79 {
80 SECTION("changePassword is successful")
81 {
82 rpcPath = "/czechlight-system:authentication/users[name='root']/change-password";
Tomáš Peckad9e741f2021-02-10 15:51:17 +010083 expectation = NAMED_REQUIRE_CALL(mock, changePassword("root", "new-password", etc_shadow));
Václav Kubernátbabbab92021-01-27 09:25:05 +010084 expected = {
85 {"/result", "success"}
86 };
87 input = {
88 {"password-cleartext", "new-password"}
89 };
90 }
91
92 SECTION("changePassword throws")
93 {
94 rpcPath = "/czechlight-system:authentication/users[name='root']/change-password";
Tomáš Peckad9e741f2021-02-10 15:51:17 +010095 expectation = NAMED_REQUIRE_CALL(mock, changePassword("root", "new-password", etc_shadow)).THROW(std::runtime_error("Task failed succesfully."));
Václav Kubernátbabbab92021-01-27 09:25:05 +010096 expected = {
97 {"/result", "failure"},
98 {"/message", "Task failed succesfully."}
99 };
100 input = {
101 {"password-cleartext", "new-password"}
102 };
103 }
104 }
105
106 auto output = rpcFromSysrepo(test_srSess, rpcPath, input);
107 REQUIRE(output == expected);
108 }
109
110 SECTION("keys")
111 {
112 std::string rpcPath;
113 std::map<std::string, std::string> input;
114 std::map<std::string, std::string> expected;
115
116 std::filesystem::path fileToCheck;
117 std::string expectedContents;
118
119 FileInjector rootKeys(testDir / "authorized_keys/root", std::filesystem::perms::owner_read | std::filesystem::perms::owner_write,
120 "ssh-rsa SOME_KEY comment\n"
121 );
122
123 FileInjector ciKeys(testDir / "authorized_keys/ci", std::filesystem::perms::owner_read | std::filesystem::perms::owner_write,
124 "ssh-rsa ci1 comment\n"
125 "ssh-rsa ci2 comment\n"
126 );
127
128 SECTION("add a key")
129 {
130 rpcPath = "/czechlight-system:authentication/users[name='root']/add-authorized-key";
131
132 expected = {
133 {"/result", "success"}
134 };
135 input = {
136 {"key", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCiBEDq8VmzBcJ23q/5GjUy8Hc18Ib20cxGEdI8McjN66eeCPc8tkeji6KT1mx15UmaJ1y+8S8cPxKi2ycdUyFpuXijDkgpuwbd3XYsOQQvMarNhyzEP7SoK5xhMy0Rcgw0Ep57JMDCEaO/V7+4lK4Mu1e+e+CyR5gVg5anGnROlRElr7h18fqCMf1JNW1tZcK5xyfUqYqnkCMKrjIFCOKqZlSo1UVJaKgWNvMx+snrBAsCUvK4N7uKniDMGt4foJBfSNQ60T1UWREbeK5B/dRnmuWJB2P43oWZB0aeEbiBpM/kGh6TE22SmTutpAk/bsgfGd6TKyOuyhkyjITbixo3F5QJ7an8LtF4Uau8CLCs14lRORBeI7a5RpZnfD/TJJ+OvpDm1LKJO3ZlILO0achrkUT1O2urM4tc6O7Fik2QjGUC9QkL4AHXIDDGjpg1or56zoR8W9Tmng6/2+8SGm4n/qxtfoifYyxqPJVUya0zwmAjkoyofoyBtrktzlH4qk= comment"}
137 };
138
139 expectedContents = "ssh-rsa SOME_KEY comment\n"
140 "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCiBEDq8VmzBcJ23q/5GjUy8Hc18Ib20cxGEdI8McjN66eeCPc8tkeji6KT1mx15UmaJ1y+8S8cPxKi2ycdUyFpuXijDkgpuwbd3XYsOQQvMarNhyzEP7SoK5xhMy0Rcgw0Ep57JMDCEaO/V7+4lK4Mu1e+e+CyR5gVg5anGnROlRElr7h18fqCMf1JNW1tZcK5xyfUqYqnkCMKrjIFCOKqZlSo1UVJaKgWNvMx+snrBAsCUvK4N7uKniDMGt4foJBfSNQ60T1UWREbeK5B/dRnmuWJB2P43oWZB0aeEbiBpM/kGh6TE22SmTutpAk/bsgfGd6TKyOuyhkyjITbixo3F5QJ7an8LtF4Uau8CLCs14lRORBeI7a5RpZnfD/TJJ+OvpDm1LKJO3ZlILO0achrkUT1O2urM4tc6O7Fik2QjGUC9QkL4AHXIDDGjpg1or56zoR8W9Tmng6/2+8SGm4n/qxtfoifYyxqPJVUya0zwmAjkoyofoyBtrktzlH4qk= comment\n";
141
142 fileToCheck = testDir / "authorized_keys/root";
143
144 auto result = rpcFromSysrepo(test_srSess, rpcPath, input);
145 REQUIRE(result == expected);
146 }
147
148 SECTION("adding invalid key")
149 {
150 rpcPath = "/czechlight-system:authentication/users[name='root']/add-authorized-key";
151
152 expected = {
153 {"/result", "failure"},
154 {"/message", "Key is not a valid SSH public key: " SSH_KEYGEN_EXECUTABLE " returned non-zero exit code 255\nssh-rsa INVALID comment"}
155 };
156 input = {
157 {"key", "ssh-rsa INVALID comment"}
158 };
159
160 expectedContents = "ssh-rsa SOME_KEY comment\n";
161
162 fileToCheck = testDir / "authorized_keys/root";
163
164 auto result = rpcFromSysrepo(test_srSess, rpcPath, input);
165 REQUIRE(result == expected);
166 }
167
168 SECTION("remove key")
169 {
170 rpcPath = "/czechlight-system:authentication/users[name='ci']/authorized-keys[index='0']/remove";
171
172 expected = {
173 {"/result", "success"},
174 };
175
176 expectedContents = "ssh-rsa ci2 comment\n";
177
178 fileToCheck = testDir / "authorized_keys/ci";
179
180 auto result = rpcFromSysrepo(test_srSess, rpcPath, input);
181 REQUIRE(result == expected);
182
183 }
184
185 SECTION("remove last key")
186 {
187 rpcPath = "/czechlight-system:authentication/users[name='root']/authorized-keys[index='0']/remove";
188
189 expected = {
190 {"/result", "failure"},
191 {"/message", "Can't remove last key."},
192 };
193
194 expectedContents = "ssh-rsa SOME_KEY comment\n";
195
196 fileToCheck = testDir / "authorized_keys/root";
197
198 auto result = rpcFromSysrepo(test_srSess, rpcPath, input);
199 REQUIRE(result == expected);
200 }
201
202
203 REQUIRE(velia::utils::readFileToString(fileToCheck) == expectedContents);
204 }
205
206}