File: check-client

package info (click to toggle)
cockpit 356-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 317,176 kB
  • sloc: javascript: 775,374; python: 41,133; ansic: 33,875; cpp: 11,141; sh: 3,534; makefile: 581; xml: 262
file content (245 lines) | stat: -rwxr-xr-x 10,029 bytes parent folder | download | duplicates (2)
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)

#
# Copyright (C) 2023 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later


import testlib


# enable this once our cockpit/ws container can beiboot
@testlib.skipWsContainer("client setup does not work with ws container")
class TestClient(testlib.MachineCase):

    provision = {
        "client": {"address": "10.111.113.1/24"},
        "target": {"address": "10.111.113.2/24"},
    }

    def setUp(self):
        super().setUp()
        self.m_target = self.machines["target"]
        self.m_client = self.machines["client"]
        self.m_target.execute("hostnamectl set-hostname target")
        # validate on-demand install: this does not work on arch, non-split package
        if self.m_target.image.startswith("debian") or self.m_target.image.startswith("ubuntu"):
            self.m_target.execute("dpkg --purge pcp")
        elif self.m_target.image == 'arch':
            self.m_target.execute("pacman -Rdd --noconfirm pcp")
        else:
            self.m_target.execute("""
                rpm --erase --verbose pcp python3-pcp
                systemctl daemon-reload
            """)

        # replicate the plumbing bits of src/client/cockpit-client to set up cockpit-beiboot
        self.m_client.write("/etc/cockpit/cockpit.conf", """
[WebService]
X-For-CockpitClient = true
LoginTo = true

[Ssh-Login]
Command = /usr/bin/env python3 -m cockpit.beiboot
""")
        # use the connect_from_flatpak() code path of beiboot.py
        self.m_client.write("/.flatpak-info", "test mock")
        self.m_client.write("/usr/local/bin/flatpak-spawn", """#!/bin/sh -eux
if [ "$1" = "--host" ]; then shift; continue; fi
while [ "${1#--env=}" != "$1" ]; do
    export "${1#--env=}"
    shift
    continue
done
exec "$@"
""", perm="0755")

    def check_login(self, expected_user: str) -> None:
        b = self.browser
        b.wait_visible('#content')
        b.wait_in_text("#hosts-sel", expected_user)
        b.enter_page("/system")
        if self.is_devel_build():
            b.wait_visible("#system_information_hostname_text")
            # devel disables preloads
        else:
            b.wait_visible("#page_status_notification_updates")
        b.switch_to_top()

    def logout(self, check_last_host: str | None = None) -> None:
        b = self.browser

        b.logout()
        # FIXME: This is broken, nothing appears
        # b.wait_text("#brand", "Connect to:")
        if check_last_host:
            b.wait_val("#server-field", check_last_host)

        # no leaked processes
        self.m_client.execute('''
            while [ -n "$(pgrep -au admin | grep -Ev 'cockpit-ws' >&2)" ]; do sleep 1; done
        ''', timeout=10)
        self.m_target.execute("while pgrep -af '([c]ockpit|[s]sh-agent)' >&2; do sleep 1; done",
                              timeout=30)

    def testBeibootNoBridge(self):
        # set up target machine: no cockpit
        self.m_target.execute("rm /usr/bin/cockpit-bridge; rm -r /usr/share/cockpit")
        self.checkLoginScenarios()

    def testBeibootWithBridge(self):
        self.checkLoginScenarios()

    def checkLoginScenarios(self):
        self.m_client.spawn(f"runuser -u admin -- {self.libexecdir}/cockpit-ws --no-tls", "ws.log")
        self.m_client.wait_for_cockpit_running()

        b = self.browser
        b.open("/")

        # same username + password login, unknown host key
        b.wait_text("#brand", "Connect to:")
        b.wait_not_visible("#recent-hosts-list")
        b.set_val("#server-field", "10.111.113.2")
        b.click("#login-button")
        # accept unknown host key
        b.wait_in_text("#hostkey-message-1", "10.111.113.2")
        b.wait_in_text("#hostkey-fingerprint", "SHA256:")
        b.click("#login-button")
        b.wait_text("#conversation-prompt", "admin@10.111.113.2's password: ")
        b.set_val("#conversation-input", "foobar")
        b.click("#login-button")
        self.check_login("admin@target")
        b.wait_in_text("#host-apps", "Services")
        b.wait_in_text("#host-apps", "Terminal")
        b.become_superuser()
        b.drop_superuser()
        self.logout(check_last_host="10.111.113.2")

        # remembers the last host it connected to
        b.wait_in_text("#recent-hosts-list", "10.111.113.2")

        # same username + password login, now host is known
        b.click("#login-button")
        b.wait_text("#conversation-prompt", "admin@10.111.113.2's password: ")
        b.set_val("#conversation-input", "foobar")
        b.click("#login-button")
        self.check_login("admin@target")
        b.become_superuser()

        # python3-pcp is installed on client, but not on target; recognize that
        b.go("/metrics")
        b.enter_page("/metrics")
        b.wait_in_text(".pf-v6-c-empty-state", "PCP is missing")
        # on-demand install is allowed
        b.wait_in_text(".pf-v6-c-empty-state button.pf-m-primary", "Install PCP support")
        b.click(".pf-v6-c-empty-state button.pf-m-primary")
        b.wait_in_text(".pf-v6-c-modal-box", "pcp will be installed")
        b.click(".pf-v6-c-modal-box button.cancel")

        b.drop_superuser()
        self.logout()

        # wrong password, SSH gives you three attempts
        b.click("#login-button")
        for _ in range(3):
            b.wait_text("#conversation-prompt", "admin@10.111.113.2's password: ")
            b.set_val("#conversation-input", "wrong")
            b.click("#login-button")
        b.wait_in_text("#login-error-title", "Authentication failed")
        b.wait_val("#server-field", "10.111.113.2")

        # connect to most recent host
        b.open("/")  # reset URL from /metrics and last remote =host
        b.click("#recent-hosts-list .host-line button.host-name")
        b.wait_text("#conversation-prompt", "admin@10.111.113.2's password: ")
        b.set_val("#conversation-input", "foobar")
        b.click("#login-button")
        self.check_login("admin@target")
        self.logout()

        # different user name + password
        self.m_target.execute("useradd -s /bin/bash user; echo user:barfoo | chpasswd")
        b.set_val("#server-field", "user@10.111.113.2")
        b.click("#login-button")
        b.wait_text("#conversation-prompt", "user@10.111.113.2's password: ")
        b.set_val("#conversation-input", "barfoo")
        b.click("#login-button")
        self.check_login("user@target")

        # not a sudoer
        b.open_superuser_dialog()
        b.wait_text(".pf-v6-c-modal-box__title-text", "Switch to administrative access")
        b.set_input_text(".pf-v6-c-modal-box input", "barfoo")
        b.click(".pf-v6-c-modal-box button:contains('Authenticate')")
        b.wait_text(".pf-v6-c-modal-box__title-text", "Problem becoming administrator")
        b.click(".pf-v6-c-modal-box button.btn-cancel")
        b.wait_not_present(".pf-v6-c-modal-box")
        b.check_superuser_indicator("Limited access")

        self.logout()
        b.wait_in_text("#recent-hosts-list", "user@10.111.113.2")

        # explicit port
        b.set_val("#server-field", "10.111.113.2:22")
        b.click("#login-button")
        b.wait_text("#conversation-prompt", "admin@10.111.113.2's password: ")
        b.set_val("#conversation-input", "foobar")
        b.click("#login-button")
        self.check_login("admin@target")
        self.logout()
        b.wait_in_text("#recent-hosts-list", "10.111.113.2:22")

        # different user name + explicit port
        b.set_val("#server-field", "user@10.111.113.2:22")
        b.click("#login-button")
        b.wait_text("#conversation-prompt", "user@10.111.113.2's password: ")
        b.set_val("#conversation-input", "barfoo")
        b.click("#login-button")
        self.check_login("user@target")
        self.logout()
        b.wait_in_text("#recent-hosts-list", "user@10.111.113.2:22")

        # remove that recent hosts entry, this also avoids ambiguous selectors further down
        b.click("#recent-hosts-list .host-line[data-host-id='user@10.111.113.2:22'] button.host-remove")
        b.wait_not_in_text("#recent-hosts-list", "user@10.111.113.2:22")

        # unreachable host
        b.set_val("#server-field", "unknownhost")
        b.click("#login-button")
        b.wait_text("#login-error-title", "Refusing to connect")
        b.wait_text("#login-error-message", "Host is unknown")
        b.wait_val("#server-field", "unknownhost")
        # does not appear in recent hosts
        b.wait_in_text("#recent-hosts-list", "10.111.113.2")
        self.assertNotIn("unknownhost", b.text("#recent-hosts-list"))

        # wrong port
        b.set_val("#server-field", "10.111.113.2:222")
        b.click("#login-button")
        b.wait_in_text("#login-error-message", "Host is unknown")

        # unencrypted SSH key login
        self.m_client.execute("runuser -u admin -- ssh-keygen -t rsa -N '' -f ~admin/.ssh/id_rsa")
        pubkey = self.m_client.execute("cat ~admin/.ssh/id_rsa.pub")
        self.m_target.write("/home/user/.ssh/authorized_keys", pubkey, owner="user:user", perm="600")
        b.click("#recent-hosts-list .host-line[data-host-id='user@10.111.113.2'] button.host-name")
        self.check_login("user@target")
        self.logout()

        # encrypted SSH key login
        self.m_client.execute("runuser -u admin -- ssh-keygen -f ~admin/.ssh/id_rsa -p -P '' -N foobarfoo")
        b.click("#login-button")
        b.wait_text("#conversation-prompt", "Enter passphrase for key '/home/admin/.ssh/id_rsa': ")
        b.set_val("#conversation-input", "wrong")
        b.click("#login-button")
        b.wait_val("#conversation-input", "")
        b.wait_text("#conversation-prompt", "Enter passphrase for key '/home/admin/.ssh/id_rsa': ")
        b.set_val("#conversation-input", "foobarfoo")
        b.click("#login-button")
        self.check_login("user@target")
        self.logout()


if __name__ == '__main__':
    testlib.test_main()