예제 #1
0
    def migrate(self):
        if isK8S():
            return

        cmd = ["usr/bin/tenantadm", "migrate"]

        self.container_manager.execute(self.cid, cmd)
예제 #2
0
 def cleanup(self):
     if isK8S():
         return
     dbs = self.client.list_database_names()
     dbs = [d for d in dbs if d not in ["local", "admin", "config", "workflows"]]
     for d in dbs:
         self.client.drop_database(d)
예제 #3
0
    def propagate_inventory_statuses(self, tenant_id=None):
        if isK8S():
            return

        cmd = ["usr/bin/deviceauth", "propagate-inventory-statuses"]

        if tenant_id is not None:
            cmd.extend(["--tenant_id", tenant_id])

        self.container_manager.execute(self.cid, cmd)
예제 #4
0
    def migrate(self, tenant_id=None):
        if isK8S():
            return

        cmd = [self.path, "migrate"]

        if tenant_id is not None:
            cmd.extend(["--tenant", tenant_id])

        self.container_manager.execute(self.cid, cmd)
예제 #5
0
    def __init__(self, microservice, containers_namespace, container_manager):
        if isK8S():
            self.container_manager = KubernetesNamespace()
            base_filter = microservice
        elif container_manager is None:
            self.container_manager = DockerNamespace(containers_namespace)
            base_filter = microservice + "_1"
        else:
            self.container_manager = container_manager
            base_filter = microservice + "_1"

        self.cid = self.container_manager.getid([base_filter])
예제 #6
0
 def call(
     self,
     method,
     url,
     body=None,
     data=None,
     path_params={},
     qs_params={},
     headers={},
     auth=None,
     files=None,
 ):
     url = self.__make_url(url)
     url = self.__subst_path_params(url, path_params)
     try:
         p = None
         if isK8S() and url.startswith("http://mender-"):
             host_forward_port = get_free_tcp_port()
             host = self.host.split(":", 1)[0]
             port = self.host.split(":", 1)[1] if ":" in self.host else "80"
             cmd = [
                 "kubectl",
                 "port-forward",
                 "service/" + host,
                 "%d:%s" % (host_forward_port, port),
             ]
             p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL)
             url = ("http://localhost:%d/" % host_forward_port) + url.split(
                 "/", 3)[-1]
             wait_for_port(port=host_forward_port,
                           host="localhost",
                           timeout=10.0)
         with warnings.catch_warnings():
             warnings.simplefilter("ignore",
                                   category=InsecureRequestWarning)
             return requests.request(
                 method,
                 url,
                 json=body,
                 data=data,
                 params=qs_params,
                 headers=self.__make_headers(headers),
                 auth=auth,
                 verify=False,
                 files=files,
             )
     finally:
         if p is not None:
             p.terminate()
예제 #7
0
def update_tenant(tid, addons=None, plan=None, container_manager=None):
    """Call internal PUT tenantadm/tenants/{tid}"""
    update = {}
    if addons is not None:
        update["addons"] = tenantadm.make_addons(addons)

    if plan is not None:
        update["plan"] = plan

    tenantadm_host = (
        tenantadm.HOST
        if isK8S() or container_manager is None
        else container_manager.get_ip_of_service("mender-tenantadm")[0] + ":8080"
    )
    tadm = ApiClient(tenantadm.URL_INTERNAL, host=tenantadm_host, schema="http://")
    res = tadm.call(
        "PUT", tenantadm.URL_INTERNAL_TENANT, body=update, path_params={"tid": tid},
    )
    assert res.status_code == 202
예제 #8
0
 def call(
     self,
     method,
     url,
     body=None,
     data=None,
     path_params={},
     qs_params={},
     headers={},
     auth=None,
     files=None,
 ):
     url = self.__make_url(url)
     url = self.__subst_path_params(url, path_params)
     try:
         p = None
         if isK8S() and url.startswith("http://mender-"):
             host = self.host.split(":", 1)[0]
             port = self.host.split(":", 1)[1] if ":" in self.host else "80"
             cmd = [
                 "kubectl", "port-forward", "service/" + host,
                 "8080:%s" % port
             ]
             p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL)
             url = "http://localhost:8080/" + url.split("/", 3)[-1]
             # wait a few seconds to let the port-forwarding fully initialize
             time.sleep(3)
         return requests.request(
             method,
             url,
             json=body,
             data=data,
             params=qs_params,
             headers=self.__make_headers(headers),
             auth=auth,
             verify=False,
             files=files,
         )
     finally:
         if p is not None:
             p.terminate()
예제 #9
0
def wait_for_traefik(gateway_host, routers=[]):
    """ Wait until provided routers are installed.
    Prevents race conditions where services are already up but traefik hasn't yet registered their routers. This causes subtle timing issues.
    By default checks the basic routers (incl. deployments - startup so time consuming, in practice it guarantees success).
    """
    if isK8S():
        return
    if routers == []:
        rnames = [
            "deployments@docker",
            "deploymentsMgmt@docker",
            "minio@docker",
            "deviceauth@docker",
            "deviceauthMgmt@docker",
            "inventoryMgmt@docker",
            "inventoryMgmtV1@docker",
            "useradm@docker",
            "useradmLogin@docker",
            "deviceauth@docker",
            "deviceauthMgmt@docker",
            "inventoryV1@docker",
        ]
    else:
        rnames = routers[:]

    for _ in redo.retrier(attempts=5, sleeptime=10):
        try:
            r = requests.get(
                "http://{}:8080/api/http/routers".format(gateway_host))
            assert r.status_code == 200

            cur_routers = [x["name"] for x in r.json()]

            if set(cur_routers).issuperset(set(rnames)):
                break
        except requests.exceptions.ConnectionError as ex:
            print("connection error while waiting for routers - but that's ok")
    else:
        assert False, "timeout hit waiting for traefik routers {}".format(
            rnames)
예제 #10
0
 def _get_endpoint_url(url):
     global forward_port
     if isK8S() and url.startswith("http://mender-"):
         url_parsed = urlparse(url)
         host = url_parsed.hostname
         port = url_parsed.port
         _, host_forward_port = processes.get(host, (None, None))
         if host_forward_port is None:
             forward_port += 1
             host_forward_port = forward_port
             cmd = [
                 "kubectl",
                 "port-forward",
                 "service/" + host,
                 "%d:%d" % (host_forward_port, port),
             ]
             p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL)
             processes[host] = (p, host_forward_port)
             # wait a few seconds to let the port-forwarding fully initialize
             time.sleep(3)
         url = ("http://localhost:%d" % host_forward_port) + url_parsed.path
     return url
예제 #11
0
 def _get_endpoint_url(url):
     global forward_port
     if isK8S() and url.startswith("http://mender-"):
         url_parsed = urlparse(url)
         host = url_parsed.hostname
         port = url_parsed.port
         _, host_forward_port = processes.get(host, (None, None))
         if host_forward_port is None:
             host_forward_port = get_free_tcp_port()
             cmd = [
                 "kubectl",
                 "port-forward",
                 "service/" + host,
                 "%d:%d" % (host_forward_port, port),
             ]
             p = subprocess.Popen(cmd, stdout=subprocess.DEVNULL)
             processes[host] = (p, host_forward_port)
             wait_for_port(port=host_forward_port,
                           host="localhost",
                           timeout=10.0)
         url = ("http://localhost:%d" % host_forward_port) + url_parsed.path
     return url
예제 #12
0
class _TestRemoteTerminalBase:
    def test_regular_protocol_commands(self, docker_env):
        self.assert_env(docker_env)

        with docker_env.devconnect.get_websocket() as ws:
            # Start shell.
            shell = proto_shell.ProtoShell(ws)
            body = shell.startShell()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            assert body == proto_shell.MSG_BODY_SHELL_STARTED

            # Drain any initial output from the prompt. It should end in either "# "
            # (root) or "$ " (user).
            output = shell.recvOutput()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            assert output[-2:].decode() in [
                "# ",
                "$ ",
            ], "Could not detect shell prompt."

            # Starting the shell again should be a no-op. It should return that
            # it is already started, as long as the shell limit is 1. MEN-4240.
            body = shell.startShell()
            assert shell.protomsg.props["status"] == protomsg.PROP_STATUS_ERROR
            assert body == b"failed to start shell: shell is already running"

            # Make sure we do not get any new output, it should be the same shell as before.
            output = shell.recvOutput()
            assert (
                output == b""
            ), "Unexpected output received when relauncing already launched shell."

            # Test if a simple command works.
            shell.sendInput("ls /\n".encode())
            output = shell.recvOutput()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            output = output.decode()
            assert "usr" in output
            assert "etc" in output

            # Try to stop shell.
            body = shell.stopShell()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            assert body is None

            # Repeat stopping and verify the error
            body = shell.stopShell()
            assert shell.protomsg.props["status"] == protomsg.PROP_STATUS_ERROR
            assert b"session not found" in body, body

            # Make sure we can not send anything to the shell.
            shell.sendInput("ls /\n".encode())
            output = shell.recvOutput()
            assert shell.protomsg.props["status"] == protomsg.PROP_STATUS_ERROR
            output = output.decode()
            assert "usr" not in output
            assert "etc" not in output
            assert "session not found" in output, output

            # Start it again.
            shell.startShell()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL

            # Drain any initial output from the prompt. It should end in either "# "
            # (root) or "$ " (user).
            output = shell.recvOutput()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            assert output[-2:].decode() in [
                "# ",
                "$ ",
            ], "Could not detect shell prompt."

    def test_dbus_reconnect(self, docker_env):
        self.assert_env(docker_env)

        with docker_env.devconnect.get_websocket():
            # Nothing to do, just connecting successfully is enough.
            pass

        # Test that mender-connect recovers if it initially has no DBus
        # connection. This is important because we don't have DBus activation
        # enabled in the systemd service file, so it's a race condition who gets
        # to the DBus service first.
        client_service_name = docker_env.device.get_client_service_name()
        docker_env.device.run(
            f"systemctl --job-mode=ignore-dependencies stop {client_service_name}"
        )
        docker_env.device.run(
            "systemctl --job-mode=ignore-dependencies restart mender-connect")

        time.sleep(10)

        # At this point, mender-connect will already have queried DBus.
        docker_env.device.run(
            f"systemctl --job-mode=ignore-dependencies start {client_service_name}"
        )

        with docker_env.devconnect.get_websocket():
            # Nothing to do, just connecting successfully is enough.
            pass

    @pytest.mark.skipif(
        isK8S(), reason="not testable in a staging or production environment")
    def test_websocket_reconnect(self, docker_env):
        self.assert_env(docker_env)

        with docker_env.devconnect.get_websocket():
            # Nothing to do, just connecting successfully is enough.
            pass

        # Test that mender-connect recovers if it loses the connection to deviceconnect.
        docker_env.restart_service("mender-deviceconnect")

        time.sleep(10)

        with docker_env.devconnect.get_websocket():
            # Nothing to do, just connecting successfully is enough.
            pass

    def test_bogus_shell_message(self, docker_env):
        self.assert_env(docker_env)

        with docker_env.devconnect.get_websocket() as ws:
            prot = protomsg.ProtoMsg(proto_shell.PROTO_TYPE_SHELL)

            prot.clear()
            prot.setTyp("bogusmessage")
            msg = prot.encode(b"")
            ws.send(msg)

            msg = ws.recv()
            prot.decode(msg)
            assert prot.props["status"] == protomsg.PROP_STATUS_ERROR
            assert prot.protoType == proto_shell.PROTO_TYPE_SHELL
            assert prot.typ == "bogusmessage"

    def test_session_recording(self, docker_env):
        self.assert_env(docker_env)

        def get_cmd(ws, timeout=1):
            pmsg = protomsg.ProtoMsg(proto_shell.PROTO_TYPE_SHELL)
            body = b""
            try:
                while True:
                    msg = ws.recv(timeout)
                    b = pmsg.decode(msg)
                    if pmsg.typ == proto_shell.MSG_TYPE_SHELL_COMMAND:
                        body += b
            except TimeoutError:
                return body

        session_id = ""
        session_bytes = b""
        with docker_env.devconnect.get_websocket() as ws:
            # Start shell.
            shell = proto_shell.ProtoShell(ws)
            body = shell.startShell()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            assert body == proto_shell.MSG_BODY_SHELL_STARTED

            assert shell.sid is not None
            session_id = shell.sid
            """ Record a series of commands """
            shell.sendInput("echo 'now you see me'\n".encode())
            session_bytes += get_cmd(ws)
            # Disable echo
            time.sleep(1)
            shell.sendInput("stty -echo\n".encode())
            time.sleep(1)
            session_bytes += get_cmd(ws)
            shell.sendInput('echo "now you don\'t" > /dev/null\n'.encode())
            session_bytes += get_cmd(ws)
            shell.sendInput("# Invisible comment\n".encode())
            session_bytes += get_cmd(ws)
            # Turn echo back on
            time.sleep(1)
            shell.sendInput("stty echo\n".encode())
            time.sleep(1)
            session_bytes += get_cmd(ws)
            shell.sendInput("echo 'and now echo is back on'\n".encode())
            session_bytes += get_cmd(ws)

            body = shell.stopShell()
            assert shell.protomsg.props[
                "status"] == protomsg.PROP_STATUS_NORMAL
            assert body is None

        # Sleep for a second to make sure the session log propagate to the DB.
        time.sleep(1)

        playback_bytes = b""
        with docker_env.devconnect.get_playback_websocket(session_id,
                                                          sleep_ms=0) as ws:
            playback_bytes = get_cmd(ws)

        assert playback_bytes == session_bytes

        assert b"now you see me" in playback_bytes
        assert b"echo 'now you see me'" in playback_bytes

        # Check that the commands after echo was disabled is not present in the log
        assert b"# Invisible comment" not in playback_bytes
        assert b'echo "now you don\'t" > /dev/null' not in playback_bytes

        # ... and after echo is enabled
        assert b"echo 'and now echo is back on'" in playback_bytes

    def assert_env(self, docker_env):
        """Check extra env vars  used by base test funcs - make sure they're set.
        Mostly important for custom setups.
        """
        assert (docker_env.device
                is not None), "docker_env must have a designated 'device'"
        assert (
            docker_env.devconnect
            is not None), "docker_env must have a set up 'devconnect' instance"
예제 #13
0
    def test_saved_filters_dynamic(self, clean_mongo, test_case):
        """
        Check that the saved filters return the correct set of
        devices when the inventory changes dynamically. That is,
        when devices modify their inventory or get removed entirely.
        """

        devauthm = ApiClient(deviceauth.URL_MGMT)
        usradmm = ApiClient(useradm.URL_MGMT)
        invm_v2 = ApiClient(inventory_v2.URL_MGMT)
        invd = ApiClient(inventory.URL_DEV)

        # Initial device inventory setup
        inventories = [
            {"deb": "squeeze", "py3": 3.3, "py2": 2.7, "idx": 0},
            {"deb": "squeeze", "py3": 3.5, "py2": 2.7, "idx": 1},
            {"deb": "wheezy", "py2": 2.7, "idx": 2},
            {"deb": "wheezy", "py2": 2.7, "idx": 3},
            {"deb": "wheezy", "py3": 3.5, "py2": 2.7, "idx": 4},
            {"deb": "wheezy", "py3": 3.6, "py2": 2.7, "idx": 5},
            {"deb": "wheezy", "py3": 3.6, "idx": 6},
            {"deb": "jessie", "py3": 3.7, "idx": 7},
            {"deb": "jessie", "py3": 3.7, "idx": 8},
            {"deb": "jessie", "py3": 3.8, "idx": 9},
            {"deb": "jessie", "py3": 3.8, "idx": 10},
            {"deb": "jessie", "py3": 3.8, "idx": 11},
            {"deb": "buster", "py3": 3.8, "idx": 12},
            {"deb": "buster", "py3": 3.8, "idx": 13},
            {"deb": "buster", "py3": 3.8, "idx": 14},
            {"deb": "buster", "py3": 3.8, "idx": 15},
        ]

        self.logger.info("Running test case: %s" % test_case["name"])

        # Setup tenant and (initial) device set.
        uuidv4 = str(uuid.uuid4())
        tenant, username, password = (
            "test.mender.io-" + uuidv4,
            "some.user+" + uuidv4 + "@example.com",
            "secretsecret",
        )
        tenant = create_org(tenant, username, password, "enterprise")
        rsp = usradmm.call(
            "POST", useradm.URL_LOGIN, auth=(tenant.users[0].name, tenant.users[0].pwd),
        )
        assert rsp.status_code == 200
        tenant.api_token = rsp.text

        # Create accepted devices with inventory.
        add_devices_to_tenant(tenant, inventories)

        # Save test filter.
        rsp = invm_v2.with_auth(tenant.api_token).call(
            "POST", inventory_v2.URL_SAVED_FILTERS, body=test_case["filter_req"],
        )
        assert rsp.status_code == 201, (
            "Failed to save filter, received status code: %d" % rsp.status_code
        )
        filter_id = rsp.headers.get("Location").split("/")[-1]

        # Check that we get the exected devices from the set.
        rsp = invm_v2.with_auth(tenant.api_token).call(
            "GET", inventory_v2.URL_SAVED_FILTER_SEARCH.format(id=filter_id)
        )
        assert rsp.status_code == 200

        devs_recv = sorted([dev["id"] for dev in rsp.json()])
        devs_exct = sorted(
            [dev.id for dev in filter(test_case["filter"], tenant.devices)]
        )
        assert devs_recv == devs_exct, (
            "Unexpected device set returned by saved filters, "
            + "expected: %s, received: %s" % (devs_recv, devs_exct)
        )

        # Perform perturbations to the device set.
        # Temporary dict representation mapping device.id -> device
        devices = {}
        for dev in tenant.devices:
            devices[dev.id] = dev
        # Starting with modifications
        for fltr, change in test_case["mods"]:
            for dev in filter(fltr, devices.values()):
                for k, v in change.items():
                    dev.inventory[k] = v

                rsp = invd.with_auth(dev.token).call(
                    "PATCH",
                    inventory.URL_DEVICE_ATTRIBUTES,
                    body=dict_to_inventoryattrs(dev.inventory),
                )
                assert rsp.status_code == 200
                devices[dev.id] = dev
            # when running against staging, wait 5 seconds to avoid hitting
            # the rate limits for the devices (one inventory update / 5 seconds)
            isK8S() and time.sleep(5.0)

        # Remove devices
        for fltr in test_case["remove"]:
            for dev in filter(fltr, list(devices.values())):
                devauthm.with_auth(tenant.api_token).call(
                    "DELETE", deviceauth.URL_DEVICE.format(id=dev.id)
                )
                devices.pop(dev.id)

        tenant.devices = list(devices.values())

        # Add new devices
        add_devices_to_tenant(tenant, test_case["new"])

        # Check that we get the exected devices from the perturbed set.
        rsp = invm_v2.with_auth(tenant.api_token).call(
            "GET",
            inventory_v2.URL_SAVED_FILTER_SEARCH.format(id=filter_id),
            qs_params={"per_page": len(tenant.devices) + 1},
        )
        assert rsp.status_code == 200

        devs_recv = sorted([dev["id"] for dev in rsp.json()])
        devs_exct = sorted(
            [dev.id for dev in filter(test_case["filter"], tenant.devices)]
        )
        assert devs_recv == devs_exct, (
            "Unexpected device set returned by saved filters, "
            + "expected: %s, received: %s" % (devs_recv, devs_exct)
        )
class TestPasswordResetEnterprise:

    uc = ApiClient(useradm.URL_MGMT)

    @pytest.mark.skipif(
        isK8S(), reason="not testable in a staging or production environment"
    )
    def test_password_reset(self, clean_mongo, smtp_mock):
        uuidv4 = str(uuid.uuid4())
        tenant, email, password = (
            "test.mender.io-" + uuidv4,
            "some.user+" + uuidv4 + "@example.com",
            "secretsecret",
        )
        create_org(tenant, email, password)
        new_password = "******"

        r = self.uc.post(useradm.URL_PASSWORD_RESET_START, body={"email": email})
        assert r.status_code == 202
        # wait for the password reset email
        message = None
        for i in range(15):
            messages = smtp_mock.filtered_messages(email)
            if len(messages) > 0:
                message = messages[0]
                break
            time.sleep(1)
        # be sure we received the email
        assert message is not None
        assert message.data != ""
        # extract the secret hash from the link
        match = re.search(
            r"https://hosted.mender.io/ui/#/password/([a-z0-9\-]+)",
            message.data.decode("utf-8"),
        )
        secret_hash = match.group(1)
        assert secret_hash != ""
        # reset the password
        r = self.uc.post(
            useradm.URL_PASSWORD_RESET_COMPLETE,
            body={"secret_hash": secret_hash, "password": new_password},
        )
        assert r.status_code == 202
        # try to login using the new password
        r = self.uc.call("POST", useradm.URL_LOGIN, auth=(email, new_password))
        assert r.status_code == 200
        assert bool(r.text)

    def test_password_reset_non_existent_email(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_PASSWORD_RESET_START,
            body={"email": "*****@*****.**"},
        )
        assert r.status_code == 202

    def test_password_reset_empty_email(self, clean_mongo):
        r = self.uc.post(useradm.URL_PASSWORD_RESET_START, body={"email": ""})
        assert r.status_code == 400

    def test_password_reset_invalid_body(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_PASSWORD_RESET_START, body={"email": ["*****@*****.**"]}
        )
        assert r.status_code == 400

    def test_password_reset_complete_invalid_secret(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_PASSWORD_RESET_COMPLETE,
            body={"secret_hash": "dummy", "password": "******"},
        )
        assert r.status_code == 400

    def test_password_reset_complete_invalid_body(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_PASSWORD_RESET_COMPLETE,
            body={"secret_hash": ["dummy"], "password": "******"},
        )
        assert r.status_code == 400
예제 #15
0
    def migrate(self):
        if isK8S():
            return

        self.container_manager.execute(self.cid, [self.path, "migrate"])
예제 #16
0
            "test.mender.io-" + uuidv4,
            "some.user+" + uuidv4 + "@foo.com",
            "secretsecret",
        )
        # Create tenant with two users
        tenant = create_org(tenant, username, password, "enterprise")
        tenant.users.append(
            create_user("some.other.user+" + uuidv4 + "@foo.com", password,
                        tenant.id))
        tenants.append(tenant)

    yield tenants


@pytest.mark.skipif(
    isK8S(), reason="not testable in a staging or production environment")
class Test2FAEnterprise:
    def _login(self, user, totp=None):
        body = {}
        if totp is not None:
            body = {"token2fa": totp}

        r = uadm.call("POST",
                      useradm.URL_LOGIN,
                      auth=(user.name, user.pwd),
                      body=body)
        return r

    def _verify(self, utoken, totp):
        body = {"token2fa": totp}
예제 #17
0
class TestVerifyEmailEnterprise:

    uc = ApiClient(useradm.URL_MGMT)

    @pytest.mark.skipif(
        isK8S(), reason="not testable in a staging or production environment"
    )
    def test_verify_email(self, clean_mongo, smtp_mock):
        uuidv4 = str(uuid.uuid4())
        tenant, email, password = (
            "test.mender.io-" + uuidv4,
            "some.user+" + uuidv4 + "@example.com",
            "secretsecret",
        )
        create_org(tenant, email, password)

        # login and try to enable two factor authentication
        # it shouldn't be possible since the user email address
        # has not been verified
        r = self.uc.call("POST", useradm.URL_LOGIN, auth=(email, password))
        assert r.status_code == 200
        utoken = r.text
        r = self.uc.with_auth(utoken).call(
            "POST", useradm.URL_SETTINGS, body={"2fa": "enabled"}
        )
        assert r.status_code == 403

        # verify user email address
        r = self.uc.post(useradm.URL_VERIFY_EMAIL_START, body={"email": email})
        assert r.status_code == 202
        # wait for the verification email
        message = None
        for i in range(15):
            messages = smtp_mock.filtered_messages(email)
            if len(messages) > 0:
                message = messages[0]
                break
            time.sleep(1)
        # be sure we received the email
        assert message is not None
        assert message.data != ""
        # extract the secret hash from the link
        match = re.search(
            r"https://hosted.mender.io/ui/#/activate/([a-z0-9\-]+)",
            message.data.decode("utf-8"),
        )
        secret_hash = match.group(1)
        assert secret_hash != ""
        # complete the email address
        r = self.uc.post(
            useradm.URL_VERIFY_EMAIL_COMPLETE, body={"secret_hash": secret_hash},
        )
        assert r.status_code == 204

        # try to enable two factor authentication after email address verification
        # now it should be possible
        r = self.uc.with_auth(utoken).call(
            "POST", useradm.URL_SETTINGS, body={"2fa": "enabled"}
        )
        assert r.status_code == 201

    def test_verify_email_non_existent_email(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_VERIFY_EMAIL_START,
            body={"email": "*****@*****.**"},
        )
        assert r.status_code == 202

    def test_verify_email_empty_email(self, clean_mongo):
        r = self.uc.post(useradm.URL_VERIFY_EMAIL_START, body={"email": ""})
        assert r.status_code == 400

    def test_verify_email_invalid_body(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_VERIFY_EMAIL_START, body={"email": ["*****@*****.**"]}
        )
        assert r.status_code == 400

    def test_verify_email_complete_invalid_secret(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_VERIFY_EMAIL_COMPLETE, body={"secret_hash": "dummy"},
        )
        assert r.status_code == 400

    def test_verify_email_complete_invalid_body(self, clean_mongo):
        r = self.uc.post(
            useradm.URL_VERIFY_EMAIL_COMPLETE, body={"secret_hash": ["dummy"]},
        )
        assert r.status_code == 400
예제 #18
0
            {"artifact": "v1", "idx": 0},
            {"artifact": "v1", "idx": 1},
            {"artifact": "v1", "idx": 2},
        ],
    )
    # sleep a few seconds waiting for the data propagation to the reporting service
    # and the Elasticsearch indexing to complete
    time.sleep(reporting.REPORTING_DATA_PROPAGATION_SLEEP_TIME_SECS)
    return tenant_os


@pytest.mark.skipif(
    useExistingTenant(), reason="not feasible to test with existing tenant",
)
@pytest.mark.skipif(
    isK8S(),
    reason="reporting service not deployed to staging or production environment",
)
class TestReportingSearchEnterprise:
    @property
    def logger(self):
        try:
            return self._logger
        except AttributeError:
            self._logger = logging.getLogger(self.__class__.__name__)
        return self._logger

    @pytest.mark.parametrize(
        "test_case",
        [
            # test_case0
예제 #19
0
class TestContactSupportEnterprise:
    def test_contact_support_bad_request(self, clean_mongo):
        uuidv4 = str(uuid.uuid4())
        tenant, email, password = (
            "test.mender.io-" + uuidv4,
            "some.user-" + uuidv4 + "@example.com",
            "secretsecret",
        )
        create_org(tenant, email, password)

        r = api_useradm.call("POST", useradm.URL_LOGIN, auth=(email, password))
        assert r.status_code == 200
        utoken = r.text
        r = api_tadm_v2.with_auth(utoken).call(
            "POST", tenantadm_v2.URL_CONTACT_SUPPORT, body={"foo": "bar"})
        assert r.status_code == 400

    @pytest.mark.skipif(
        isK8S(), reason="not testable in a staging or production environment")
    def test_contact_support(self, clean_mongo, smtp_mock):
        uuidv4 = str(uuid.uuid4())
        tenant, email, password = (
            "test.mender.io-" + uuidv4,
            "some.user-" + uuidv4 + "@example.com",
            "secretsecret",
        )
        org = create_org(tenant, email, password)

        r = api_useradm.call("POST", useradm.URL_LOGIN, auth=(email, password))
        assert r.status_code == 200
        utoken = r.text
        r = api_tadm_v2.with_auth(utoken).call(
            "POST",
            tenantadm_v2.URL_CONTACT_SUPPORT,
            body={
                "subject": "foo",
                "body": "bar"
            },
        )
        assert r.status_code == 202
        # wait for the email
        message = None
        for i in range(15):
            messages = smtp_mock.filtered_messages("*****@*****.**")
            if len(messages) > 0:
                message = messages[0]
                break
            time.sleep(1)

        # be sure we received the email
        assert message is not None
        assert message.data != ""

        # and the email is properly formatted
        data = message.data.decode("utf-8")
        match = re.search(
            r"Subject: ([a-z0-9\-]+)",
            data,
        )
        subject = match.group(1)
        assert re.search(r"Subject: foo", data) is not None
        assert re.search(r"From: [email protected]", data) is not None
        assert re.search(r"To: [email protected]", data) is not None
        assert re.search(r"Organization ID: " + org.id, data) is not None
        assert re.search(r"Organization name: " + tenant, data) is not None
        assert re.search(r"Plan name: os", data) is not None
        assert re.search(r"User ID: " + org.users[0].id, data) is not None
        assert re.search(r"User Email: " + email, data) is not None
        assert re.search(r"bar", data) is not None
예제 #20
0
class Authentication:
    auth_header = None

    org_name = "admin"
    username = "******"
    password = "******"

    multitenancy = isK8S()
    if isK8S():
        plan = "enterprise"
    current_tenant = {}

    def __init__(self, name=org_name, username=username, password=password):
        """
        :param org_name: Name of tenant organization
        :param username: Username - must be an email
        :param password: Password associated with tenant user
        """
        self.reset()
        self.org_name = name
        self.org_create = True
        self.username = username
        self.password = password

    def reset(self):
        # Reset all temporary values.
        self.auth_header = Authentication.auth_header
        self.org_name = Authentication.org_name
        self.username = Authentication.username
        self.password = Authentication.password
        self.multitenancy = Authentication.multitenancy
        self.current_tenant = Authentication.current_tenant

    def set_tenant(self, org_name, username, password, plan="os"):
        self.new_tenant(org_name, username, password, plan)

    def new_tenant(self, org_name, username, password, plan="os"):
        self.multitenancy = True
        self.reset_auth_token()
        self.org_name = org_name
        self.org_create = True
        self.username = username
        self.password = password
        self.plan = plan
        self.get_auth_token()

    def get_auth_token(self, create_new_user=True):
        if self.auth_header is not None:
            return self.auth_header

        # try login - the user might be in a shared db
        #             already (if not running xdist)
        r = self._do_login(self.username, self.password)

        logger.info("Getting authentication token for user %s@%s" %
                    (self.username, self.org_name))

        if create_new_user:
            if r.status_code != 200:
                if self.multitenancy and self.org_create:
                    tenant_id = self._create_org(self.org_name, self.username,
                                                 self.password, self.plan)
                    tenant_id = tenant_id.strip()

                    tenant_data = self._get_tenant_data(tenant_id)
                    tenant_data_json = json.loads(tenant_data)

                    self.current_tenant = {
                        "tenant_id": tenant_id,
                        "tenant_token": tenant_data_json["tenant_token"],
                        "name": tenant_data_json["name"],
                    }
                    self.org_create = False

                else:
                    self.create_user(self.username, self.password)

            # It might take some time for create_org to propagate the new user.
            # Retry login for a minute.
            for _ in range(60):
                r = self._do_login(self.username, self.password)
                if r.status_code == 200:
                    break
                time.sleep(1)
            assert r.status_code == 200

        return self.auth_header

    def create_user(self, username, password, tenant_id=""):
        namespace = get_container_manager().name
        cli = CliUseradm(containers_namespace=namespace)
        cli.create_user(username, password, tenant_id)

    def get_tenant_id(self):
        return self.current_tenant["tenant_id"]

    def reset_auth_token(self):
        self.auth_header = None

    def _do_login(self, username, password):
        r = requests_retry().post(
            "https://%s/api/management/%s/useradm/auth/login" %
            (get_container_manager().get_mender_gateway(), api_version),
            verify=False,
            auth=HTTPBasicAuth(username, password),
        )
        assert r.status_code == 200 or r.status_code == 401

        if r.status_code == 200:
            self.auth_header = {"Authorization": "Bearer " + str(r.text)}
        logger.info("Using Authorization headers: " + str(r.text))
        return r

    def _create_org(self, name, username, password, plan="os"):
        namespace = get_container_manager().name
        cli = CliTenantadm(containers_namespace=namespace)
        tenant_id = cli.create_org(name, username, password, plan)
        return tenant_id

    def _get_tenant_data(self, tenant_id):
        namespace = get_container_manager().name
        cli = CliTenantadm(containers_namespace=namespace)
        tenant = cli.get_tenant(tenant_id)
        return tenant
예제 #21
0
    def _make_trial_tenant(self, env):
        uuidv4 = str(uuid.uuid4())
        tname = "test.mender.io-{}-{}".format(uuidv4, "trial")
        email = "some.user+{}@example.com".format(uuidv4)

        tadmm = ApiClient(
            host=get_container_manager().get_mender_gateway(),
            base_url=tenantadm_v2.URL_MGMT,
        )

        args = {
            "organization": tname,
            "email": email,
            "password": "******",
            "name": "foo",
            "g-recaptcha-response": "dummy",
            "plan": "enterprise",
        }

        res = tadmm.call("POST", tenantadm_v2.URL_CREATE_ORG_TRIAL, body=args,)

        assert res.status_code == 202

        # get tenant id
        tenantadm_host = (
            tenantadm.HOST
            if isK8S()
            else get_container_manager().get_ip_of_service("mender-tenantadm")[0]
            + ":8080"
        )
        tadmi = ApiClient(
            host=tenantadm_host, base_url=tenantadm.URL_INTERNAL, schema="http://",
        )

        res = tadmi.call(
            "GET", tenantadm.URL_INTERNAL_TENANTS, qs_params={"username": email}
        )
        assert res.status_code == 200
        assert len(res.json()) == 1

        apitenant = res.json()[0]

        cli = CliTenantadm(containers_namespace=env.name)

        tenant = cli.get_tenant(apitenant["id"])
        tenant = json.loads(tenant)
        ttoken = tenant["tenant_token"]

        auth = Authentication(name=tname, username=email, password="******")
        auth.create_org = False
        auth.reset_auth_token()
        devauth = DeviceAuthV2(auth)

        new_tenant_client(env, "mender-client-trial", ttoken)
        devauth.accept_devices(1)

        devices = list(
            set([device["id"] for device in devauth.get_devices_status("accepted")])
        )
        assert 1 == len(devices)

        tenant = Tenant(tname, apitenant["id"], ttoken)
        u = User("", email, "correcthorse")

        tenant.users.append(u)
        tenant.device_id = devices[0]
        tenant.auth = auth
        tenant.devauth = devauth

        return tenant
예제 #22
0
            )

            assert rsp.status_code == 201

            rsp = api_deployments.call(
                "POST",
                deployments.URL_DEPLOYMENTS,
                body={
                    "artifact_name": f"{path.basename(tf.name)}",
                    "devices": [device["id"] for device in devices],
                    "name": "test-compat-deployment",
                },
            )
            assert rsp.status_code == 201

            deployment_id = rsp.headers.get("Location").split("/")[-1]
            assert_successful_deployment(api_deployments, deployment_id)


class TestClientCompatibilityOpenSource(TestClientCompatibilityBase):
    def test_compatibility(self, setup_os_compat):
        self.compatibility_test_impl(setup_os_compat)


@pytest.mark.skipif(
    isK8S(), reason="not relevant in a staging or production environment"
)
class TestClientCompatibilityEnterprise(TestClientCompatibilityBase):
    def test_enterprise_compatibility(self, setup_ent_compat):
        self.compatibility_test_impl(setup_ent_compat)
예제 #23
0
class TestMonitoringAlertsEnterprise(_TestMonitoringAlertsBase):
    useradm = ApiClient(useradm.URL_MGMT)
    devmonit = ApiClient(devicemonitor.URL_DEVICES)

    @pytest.mark.skipif(
        isK8S(),
        reason="no suitable smtp mock in staging environment",
    )
    @pytest.mark.parametrize(
        argnames="test_case",
        argvalues=(
            {
                "alerts": [
                    {
                        "name": "Something terrible may happen!",
                        "timestamp": datetime.utcnow().isoformat() + "Z",
                        "level": "CRITICAL",
                        "subject": {
                            "name": "mender-connect",
                            "status": "killed",
                            "type": "systemd.unit",
                            "details": "Something terrible actually happened!",
                        },
                    },
                ],
                "email_regex":
                "(CRITICAL|OK)",
            },
            {
                "alerts": [
                    {
                        "name": "mender-client be like",
                        "timestamp": datetime.utcnow().isoformat() + "Z",
                        "level": "OK",
                        "subject": {
                            "name": "mender-connect",
                            "status": "running",
                            "type": "systemd.unit",
                            "details": "It's all good",
                        },
                    },
                    {
                        "name": "sshd systemd unit",
                        "timestamp": datetime.utcnow().isoformat() + "Z",
                        "level": "CRITICAL",
                        "subject": {
                            "name": "sshd",
                            "status": "killed",
                            "type": "systemd.unit",
                            "details": "Well, this is awkward",
                        },
                    },
                    {
                        "name": "Go please!",
                        "timestamp": datetime.utcnow().isoformat() + "Z",
                        "level": "CRITICAL",
                        "subject": {
                            "name": "gopls",
                            "status": "killed",
                            "type": "process",
                            "details": "not again...",
                        },
                    },
                ],
                "email_regex":
                "(CRITICAL|OK)",
            },
        ),
        ids=["CRITICAL alert", "CRITICAL and OK alerts"],
    )
    def test_alerting_email(self, test_case, tenants_users_devices, smtp_mock):
        """
        Checks that each alert a device issues to the backend triggers
        an email sent to the user.
        """
        tenant = tenants_users_devices[0]
        user = tenant.users[0]
        devices = tenant.devices
        super().test_alerting_email(test_case, user, devices, smtp_mock)