Esempio n. 1
0
async def _do_greet_device(device, initial_ctx):
    async with spinner("Waiting for claimer"):
        in_progress_ctx = await initial_ctx.do_wait_peer()

    display_greeter_sas = click.style(str(in_progress_ctx.greeter_sas),
                                      fg="yellow")
    click.echo(f"Code to provide to claimer: {display_greeter_sas}")
    async with spinner("Waiting for claimer"):
        in_progress_ctx = await in_progress_ctx.do_wait_peer_trust()

    choices = in_progress_ctx.generate_claimer_sas_choices(size=3)
    for i, choice in enumerate(choices):
        display_choice = click.style(choice, fg="yellow")
        click.echo(f" {i} - {display_choice}")
    code = await aprompt(f"Select code provided by claimer",
                         type=click.Choice(
                             [str(x) for x in range(len(choices))]))
    if choices[int(code)] != in_progress_ctx.claimer_sas:
        click.secho("Wrong code provided", fg="red")
        return False

    async with spinner("Waiting for claimer"):
        in_progress_ctx = await in_progress_ctx.do_signify_trust()
        in_progress_ctx = await in_progress_ctx.do_get_claim_requests()

    granted_device_label = await aprompt(
        "New device label", default=in_progress_ctx.requested_device_label)
    async with spinner("Creating the device in the backend"):
        await in_progress_ctx.do_create_new_device(
            author=device, device_label=granted_device_label)

    return True
Esempio n. 2
0
async def _do_claim_device(initial_ctx):
    async with spinner(
            "Initializing connection with greeter for claiming device"):
        in_progress_ctx = await initial_ctx.do_wait_peer()

    choices = in_progress_ctx.generate_greeter_sas_choices(size=3)
    for i, choice in enumerate(choices):
        display_choice = click.style(choice, fg="yellow")
        click.echo(f" {i} - {display_choice}")
    code = await aprompt(f"Select code provided by greeter",
                         type=click.Choice(
                             [str(x) for x in range(len(choices))]))
    if choices[int(code)] != in_progress_ctx.greeter_sas:
        click.secho("Wrong code provided", fg="red")
        return None

    in_progress_ctx = await in_progress_ctx.do_signify_trust()
    display_claimer_sas = click.style(str(in_progress_ctx.claimer_sas),
                                      fg="yellow")
    click.echo(f"Code to provide to greeter: {display_claimer_sas}")
    async with spinner("Waiting for greeter"):
        in_progress_ctx = await in_progress_ctx.do_wait_peer_trust()

    requested_device_label = await aprompt("Device label",
                                           default=platform.node())
    async with spinner("Waiting for greeter (finalizing)"):
        new_device = await in_progress_ctx.do_claim_device(
            requested_device_label=requested_device_label)

    return new_device
Esempio n. 3
0
async def _do_greet_user(device, initial_ctx):
    async with spinner("Waiting for claimer"):
        in_progress_ctx = await initial_ctx.do_wait_peer()

    display_greeter_sas = click.style(str(in_progress_ctx.greeter_sas),
                                      fg="yellow")
    click.echo(f"Code to provide to claimer: {display_greeter_sas}")
    async with spinner("Waiting for claimer"):
        in_progress_ctx = await in_progress_ctx.do_wait_peer_trust()

    choices = in_progress_ctx.generate_claimer_sas_choices(size=3)
    for i, choice in enumerate(choices):
        display_choice = click.style(choice, fg="yellow")
        click.echo(f" {i} - {display_choice}")
    code = await aprompt(
        f"Select code provided by claimer",
        type=click.Choice([str(i) for i, _ in enumerate(choices)]),
    )
    if choices[int(code)] != in_progress_ctx.claimer_sas:
        click.secho("Wrong code provided", fg="red")
        return False

    async with spinner("Waiting for claimer"):
        in_progress_ctx = await in_progress_ctx.do_signify_trust()
        in_progress_ctx = await in_progress_ctx.do_get_claim_requests()

    granted_label = await aprompt(
        "New user label", default=in_progress_ctx.requested_human_handle.label)
    granted_email = await aprompt(
        "New user email", default=in_progress_ctx.requested_human_handle.email)
    granted_device_label = await aprompt(
        "New user device label",
        default=in_progress_ctx.requested_device_label)
    choices = list(UserProfile)
    for i, choice in enumerate(UserProfile):
        display_choice = click.style(choice.value, fg="yellow")
        click.echo(f" {i} - {display_choice}")
    choice_index = await aprompt("New user profile",
                                 default="0",
                                 type=click.Choice(
                                     [str(i) for i, _ in enumerate(choices)]))
    granted_profile = choices[int(choice_index)]
    async with spinner("Creating the user in the backend"):
        await in_progress_ctx.do_create_new_user(
            author=device,
            device_label=granted_device_label,
            human_handle=HumanHandle(email=granted_email, label=granted_label),
            profile=granted_profile,
        )

    return True
Esempio n. 4
0
async def _pki_enrollment_submit(
    config: CoreConfig,
    addr: BackendPkiEnrollmentAddr,
    requested_device_label: DeviceLabel,
    force: bool,
):
    ctx = await PkiEnrollmentSubmitterInitialCtx.new(addr)

    x509_display = f"Certificate SHA1 Fingerprint: " + click.style(
        ctx.x509_certificate.certificate_sha1.hex(), fg="yellow"
    )
    x509_display += "\nCertificate Issuer Common Name: " + click.style(
        ctx.x509_certificate.issuer_common_name, fg="yellow"
    )
    x509_display += "\nCertificate Subject Common Name: " + click.style(
        ctx.x509_certificate.subject_common_name, fg="yellow"
    )
    x509_display += "\nCertificate Subject Email Address: " + click.style(
        ctx.x509_certificate.subject_email_address, fg="yellow"
    )

    click.echo(x509_display)

    async with spinner("Sending PKI enrollment to the backend"):
        ctx = await ctx.submit(
            config_dir=config.config_dir, requested_device_label=requested_device_label, force=force
        )

    enrollment_id_display = click.style(ctx.enrollment_id.hex, fg="green")
    click.echo(f"PKI enrollment {enrollment_id_display} submitted")
async def _bootstrap_organization(
    debug, device_id, organization_bootstrap_addr, config_dir, force, password
):
    root_signing_key = SigningKey.generate()
    root_verify_key = root_signing_key.verify_key
    organization_addr = organization_bootstrap_addr.generate_organization_addr(root_verify_key)

    device_display = click.style(device_id, fg="yellow")
    device = generate_new_device(device_id, organization_addr)

    with operation(f"Creating locally {device_display}"):
        save_device_with_password(config_dir, device, password, force=force)

    now = pendulum.now()
    certified_user = certify_user(None, root_signing_key, device.user_id, device.public_key, now)
    certified_device = certify_device(None, root_signing_key, device_id, device.verify_key, now)

    async with spinner(f"Sending {device_display} to server"):
        async with backend_anonymous_cmds_factory(organization_bootstrap_addr) as cmds:
            await cmds.organization_bootstrap(
                organization_bootstrap_addr.organization_id,
                organization_bootstrap_addr.bootstrap_token,
                root_verify_key,
                certified_user,
                certified_device,
            )

    organization_addr_display = click.style(organization_addr, fg="yellow")
    click.echo(f"Organization url: {organization_addr_display}")
Esempio n. 6
0
async def _greet_invitation(config, device, token):
    async with backend_authenticated_cmds_factory(
            addr=device.organization_addr,
            device_id=device.device_id,
            signing_key=device.signing_key,
            keepalive=config.backend_connection_keepalive,
    ) as cmds:
        async with spinner("Retrieving invitation info"):
            rep = await cmds.invite_list()
            if rep["status"] != "ok":
                raise RuntimeError(f"Backend error: {rep}")
        for invitation in rep["invitations"]:
            if invitation["token"] == token:
                break
        else:
            raise RuntimeError(f"Invitation not found")

        if invitation["type"] == InvitationType.USER:
            initial_ctx = UserGreetInitialCtx(cmds=cmds, token=token)
            do_greet = partial(_do_greet_user, device, initial_ctx)
        else:
            assert invitation["type"] == InvitationType.DEVICE
            initial_ctx = DeviceGreetInitialCtx(cmds=cmds, token=token)
            do_greet = partial(_do_greet_device, device, initial_ctx)

        while True:
            try:
                greet_done = await do_greet()
                if greet_done:
                    break
            except InviteError as exc:
                click.secho(str(exc), fg="red")
            click.secho("Restarting the invitation process", fg="red")
Esempio n. 7
0
async def _bootstrap_organization(
    config: CoreConfig,
    addr: BackendOrganizationBootstrapAddr,
    device_label: Optional[DeviceLabel],
    human_label: Optional[str],
    human_email: Optional[str],
    save_device_with_selected_auth: Callable,
) -> None:
    if not human_label:
        human_label = await aprompt("User fullname")
    if not human_email:
        human_email = await aprompt("User email")
    human_handle = HumanHandle(email=human_email, label=human_label)
    if not device_label:
        device_label_raw = await aprompt("Device label",
                                         default=platform.node())
        device_label = DeviceLabel(device_label_raw)

    async with spinner("Bootstrapping organization in the backend"):
        new_device = await do_bootstrap_organization(addr,
                                                     human_handle=human_handle,
                                                     device_label=device_label)

    # We don't have to worry about overwritting an existing keyfile
    # given their names are base on the device's slughash which is intended
    # to be globally unique.

    # The organization is brand new, of course there is no existing
    # remote user manifest, hence our placeholder is non-speculative.
    await user_storage_non_speculative_init(data_base_dir=config.data_base_dir,
                                            device=new_device)
    await save_device_with_selected_auth(config_dir=config.config_dir,
                                         device=new_device)
Esempio n. 8
0
async def _invite_user(
    config: CoreConfig, device: LocalDevice, email: str, send_email: bool
) -> None:
    async with spinner("Creating user invitation"):
        async with backend_authenticated_cmds_factory(
            addr=device.organization_addr,
            device_id=device.device_id,
            signing_key=device.signing_key,
            keepalive=config.backend_connection_keepalive,
        ) as cmds:
            rep = await cmds.invite_new(
                type=InvitationType.USER, claimer_email=email, send_email=send_email
            )
            if rep["status"] != "ok":
                raise RuntimeError(f"Backend refused to create user invitation: {rep}")
            if send_email and "email_sent" in rep:
                if rep["email_sent"] != InvitationEmailSentStatus.SUCCESS:
                    click.secho("Email could not be sent", fg="red")

    action_addr = BackendInvitationAddr.build(
        backend_addr=device.organization_addr.get_backend_addr(),
        organization_id=device.organization_id,
        invitation_type=InvitationType.USER,
        token=rep["token"],
    )
    action_addr_display = click.style(action_addr.to_url(), fg="yellow")
    click.echo(f"url: {action_addr_display}")
Esempio n. 9
0
async def _claim_invitation(config, addr, password):
    async with backend_invited_cmds_factory(addr=addr) as cmds:
        try:
            async with spinner("Retrieving invitation info"):
                initial_ctx = await claimer_retrieve_info(cmds)
        except BackendConnectionRefused:
            raise RuntimeError("Invitation not found")

        if initial_ctx.greeter_human_handle:
            display_greeter = click.style(initial_ctx.greeter_human_handle,
                                          fg="yellow")
        else:
            display_greeter = click.style(initial_ctx.greeter_user_id,
                                          fg="yellow")
        click.echo(f"Invitation greeter: {display_greeter}")
        while True:
            try:
                if isinstance(initial_ctx, DeviceClaimInitialCtx):
                    new_device = await _do_claim_device(initial_ctx)
                else:
                    assert isinstance(initial_ctx, UserClaimInitialCtx)
                    new_device = await _do_claim_user(initial_ctx)
                if new_device:
                    break
            except InviteError as exc:
                click.secho(str(exc), fg="red")
            click.secho("Restarting the invitation process", fg="red")

        device_display = click.style(new_device.slughash, fg="yellow")
        with operation(f"Saving device {device_display}"):
            save_device_with_password(config.config_dir, new_device, password)
async def _create_organization(debug, name, backend_addr, administrator_token):
    async with spinner("Creating organization in backend"):
        async with backend_administrator_cmds_factory(backend_addr, administrator_token) as cmds:
            bootstrap_token = await cmds.organization_create(name)

    organization_addr = BackendOrganizationBootstrapAddr.build(backend_addr, name, bootstrap_token)
    organization_addr_display = click.style(organization_addr, fg="yellow")
    click.echo(f"Bootstrap organization url: {organization_addr_display}")
async def _bootstrap_organization(debug, device_id,
                                  organization_bootstrap_addr, config_dir,
                                  force, password):
    root_signing_key = SigningKey.generate()
    root_verify_key = root_signing_key.verify_key
    organization_addr = organization_bootstrap_addr.generate_organization_addr(
        root_verify_key)

    device_display = click.style(device_id, fg="yellow")
    device = generate_new_device(device_id,
                                 organization_addr,
                                 profile=UserProfile.ADMIN)

    with operation(f"Creating locally {device_display}"):
        save_device_with_password(config_dir, device, password, force=force)

    now = pendulum.now()
    user_certificate = UserCertificateContent(
        author=None,
        timestamp=now,
        user_id=device.user_id,
        public_key=device.public_key,
        profile=device.profile,
    )
    redacted_user_certificate = user_certificate.evolve(human_handle=None)
    device_certificate = DeviceCertificateContent(author=None,
                                                  timestamp=now,
                                                  device_id=device_id,
                                                  verify_key=device.verify_key)
    redacted_device_certificate = device_certificate.evolve(device_label=None)

    user_certificate = user_certificate.dump_and_sign(root_signing_key)
    device_certificate = device_certificate.dump_and_sign(root_signing_key)
    redacted_user_certificate = redacted_user_certificate.dump_and_sign(
        root_signing_key)
    redacted_device_certificate = redacted_device_certificate.dump_and_sign(
        root_signing_key)

    async with spinner(f"Sending {device_display} to server"):
        async with apiv1_backend_anonymous_cmds_factory(
                organization_bootstrap_addr) as cmds:
            await cmds.organization_bootstrap(
                organization_id=organization_bootstrap_addr.organization_id,
                bootstrap_token=organization_bootstrap_addr.token,
                root_verify_key=root_verify_key,
                user_certificate=user_certificate,
                device_certificate=device_certificate,
                redacted_user_certificate=redacted_user_certificate,
                redacted_device_certificate=redacted_device_certificate,
            )

    organization_addr_display = click.style(organization_addr.to_url(),
                                            fg="yellow")
    click.echo(f"Organization url: {organization_addr_display}")
Esempio n. 12
0
async def _create_organization(organization_id: OrganizationID,
                               backend_addr: BackendAddr,
                               administration_token: str) -> None:
    async with spinner("Creating organization in backend"):
        bootstrap_token = await create_organization_req(
            organization_id, backend_addr, administration_token)

    organization_addr = BackendOrganizationBootstrapAddr.build(
        backend_addr, organization_id, bootstrap_token)
    organization_addr_display = click.style(organization_addr.to_url(),
                                            fg="yellow")
    click.echo(f"Bootstrap organization url: {organization_addr_display}")
Esempio n. 13
0
        async def _migrate(db):
            async with spinner("Migrate"):
                result = await apply_migrations(db, migrations, dry_run)

            for migration in result.already_applied:
                click.secho(f"{migration.file_name} (already applied)",
                            fg="white")
            for migration in result.new_apply:
                click.secho(f"{migration.file_name} {ok}", fg="green")
            if result.error:
                migration, msg = result.error
                click.secho(f"{migration.file_name} {ko}: {msg}", fg="red")
Esempio n. 14
0
async def _do_greet_user(device: LocalDevice, initial_ctx: UserGreetInitialCtx) -> bool:
    async with spinner("Waiting for claimer"):
        in_progress_ctx = await initial_ctx.do_wait_peer()

    display_greeter_sas = click.style(str(in_progress_ctx.greeter_sas), fg="yellow")
    click.echo(f"Code to provide to claimer: {display_greeter_sas}")
    async with spinner("Waiting for claimer"):
        in_progress_ctx = await in_progress_ctx.do_wait_peer_trust()

    choices = in_progress_ctx.generate_claimer_sas_choices(size=3)
    for i, choice in enumerate(choices):
        display_choice = click.style(choice, fg="yellow")
        click.echo(f" {i} - {display_choice}")
    code = await aprompt(
        f"Select code provided by claimer",
        type=click.Choice([str(i) for i, _ in enumerate(choices)]),
    )
    if choices[int(code)] != in_progress_ctx.claimer_sas:
        click.secho("Wrong code provided", fg="red")
        return False

    async with spinner("Waiting for claimer"):
        in_progress_ctx = await in_progress_ctx.do_signify_trust()
        in_progress_ctx = await in_progress_ctx.do_get_claim_requests()

    granted_device_label, granted_human_handle, granted_profile = await ask_info_new_user(
        default_device_label=in_progress_ctx.requested_device_label,
        default_user_label=in_progress_ctx.requested_human_handle.label,
        default_user_email=in_progress_ctx.requested_human_handle.email,
    )

    async with spinner("Creating the user in the backend"):
        await in_progress_ctx.do_create_new_user(
            author=device,
            device_label=granted_device_label,
            human_handle=granted_human_handle,
            profile=granted_profile,
        )

    return True
Esempio n. 15
0
async def _claim_device(config, organization_addr, new_device_id, token,
                        password):
    async with spinner("Waiting for referee to reply"):
        device = await actual_claim_device(
            organization_addr=organization_addr,
            new_device_id=new_device_id,
            token=token,
            keepalive=config.backend_connection_keepalive,
        )

    device_display = click.style(new_device_id, fg="yellow")
    with operation(f"Saving locally {device_display}"):
        save_device_with_password(config.config_dir, device, password)
async def _create_organization(debug, name, backend_addr, administration_token, expiration_date):
    async with spinner("Creating organization in backend"):
        async with apiv1_backend_administration_cmds_factory(
            backend_addr, administration_token
        ) as cmds:
            rep = await cmds.organization_create(name, expiration_date)
            if rep["status"] != "ok":
                raise RuntimeError(f"Backend refused to create organization: {rep}")
            bootstrap_token = rep["bootstrap_token"]

    organization_addr = BackendOrganizationBootstrapAddr.build(backend_addr, name, bootstrap_token)
    organization_addr_display = click.style(organization_addr.to_url(), fg="yellow")
    click.echo(f"Bootstrap organization url: {organization_addr_display}")
Esempio n. 17
0
async def _export_recovery_device(config: CoreConfig,
                                  original_device: LocalDevice,
                                  output: Path) -> None:
    async with spinner("Creating new recovery device"):
        recovery_device = await generate_recovery_device(original_device)

    file_name = get_recovery_device_file_name(recovery_device)
    file_path = output / file_name
    file_path_display = click.style(str(file_path.absolute()), fg="yellow")
    with operation(f"Saving recovery device file in {file_path_display}"):
        passphrase = await save_recovery_device(file_path, recovery_device)

    p1 = click.style("Save the recovery passphrase in a safe place:", fg="red")
    p2 = click.style(passphrase, fg="green")
    click.echo(f"{p1} {p2}")
Esempio n. 18
0
 async def _migrate(db):
     async with spinner("Migrate"):
         result = await migrate_db(db, migrations, dry_run)
         for key, values in zip(result._fields, result):
             color = result_colors.get(key, "white")
             if key == "error":
                 if values:
                     _echo_migration(values[0], color)
                     raise click.ClickException(values[1])
             else:
                 is_done = False
                 for value in values:
                     if key in ["already_applied", "new_apply"]:
                         is_done = True
                     else:
                         is_done = False
                     _echo_migration(value, color, is_done)
Esempio n. 19
0
async def _claim_user(config, backend_addr, token, new_device_id, password,
                      pkcs11):
    async with backend_anonymous_cmds_factory(backend_addr) as cmds:

        async with spinner("Waiting for referee to reply"):
            device = await actual_claim_user(cmds, new_device_id, token)

    device_display = click.style(new_device_id, fg="yellow")
    with operation(f"Saving locally {device_display}"):
        if pkcs11:
            token_id = click.prompt("PCKS11 token id", type=int)
            key_id = click.prompt("PCKS11 key id", type=int)
            save_device_with_pkcs11(config.config_dir, device, token_id,
                                    key_id)

        else:
            save_device_with_password(config.config_dir, device, password)
Esempio n. 20
0
async def _import_recovery_device(
    config: CoreConfig,
    recovery_file: Path,
    passphrase: str,
    new_device_label: DeviceLabel,
    save_device_with_selected_auth: Callable,
) -> None:

    recovery_device = await load_recovery_device(recovery_file, passphrase)

    device_label_display = click.style(new_device_label, fg="yellow")
    async with spinner(f"Creating new device {device_label_display}"):
        new_device = await generate_new_device_from_recovery(
            recovery_device, new_device_label)

    await save_device_with_selected_auth(config_dir=config.config_dir,
                                         device=new_device)
Esempio n. 21
0
async def _invite_user(config, device, invited_user_id, admin):
    async with backend_cmds_factory(device.organization_addr, device.device_id,
                                    device.signing_key) as cmds:

        token = generate_invitation_token()

        organization_addr_display = click.style(device.organization_addr,
                                                fg="yellow")
        token_display = click.style(token, fg="yellow")
        click.echo(f"Backend url: {organization_addr_display}")
        click.echo(f"Invitation token: {token_display}")

        async with spinner("Waiting for invitation reply"):
            invite_device_id = await invite_and_create_user(
                device, cmds, invited_user_id, token, admin)

        display_device = click.style(invite_device_id, fg="yellow")
        click.echo(f"Device {display_device} has been created")
Esempio n. 22
0
async def _bootstrap_organization(config, addr, password, force):
    label = await aprompt("User fullname")
    email = await aprompt("User email")
    human_handle = HumanHandle(email=email, label=label)
    device_label = await aprompt("Device label", default=platform.node())

    async with apiv1_backend_anonymous_cmds_factory(addr=addr) as cmds:
        async with spinner("Bootstrapping organization in the backend"):
            new_device = await do_bootstrap_organization(
                cmds=cmds,
                human_handle=human_handle,
                device_label=device_label)

        device_display = click.style(new_device.slughash, fg="yellow")
        with operation(f"Saving device {device_display}"):
            save_device_with_password(config_dir=config.config_dir,
                                      device=new_device,
                                      password=password,
                                      force=force)
Esempio n. 23
0
async def _invite_device(config, device, new_device_name):
    token = generate_invitation_token()

    organization_addr_display = click.style(device.organization_addr,
                                            fg="yellow")
    token_display = click.style(token, fg="yellow")
    click.echo(f"Backend url: {organization_addr_display}")
    click.echo(f"Invitation token: {token_display}")

    async with backend_cmds_factory(device.organization_addr, device.device_id,
                                    device.signing_key) as cmds:

        async with spinner("Waiting for invitation reply"):
            await invite_and_create_device(device, cmds, new_device_name,
                                           token)

        display_device = click.style(f"{device.device_name}@{new_device_name}",
                                     fg="yellow")
        click.echo(f"Device {display_device} is ready !")
Esempio n. 24
0
async def _invite_device(config, device):
    async with spinner("Creating device invitation"):
        async with backend_authenticated_cmds_factory(
            addr=device.organization_addr,
            device_id=device.device_id,
            signing_key=device.signing_key,
            keepalive=config.backend_connection_keepalive,
        ) as cmds:
            rep = await cmds.invite_new(type=InvitationType.DEVICE)
            if rep["status"] != "ok":
                raise RuntimeError(f"Backend refused to create device invitation: {rep}")

    action_addr = BackendInvitationAddr.build(
        backend_addr=device.organization_addr,
        organization_id=device.organization_id,
        invitation_type=InvitationType.DEVICE,
        token=rep["token"],
    )
    action_addr_display = click.style(action_addr.to_url(), fg="yellow")
    click.echo(f"url: {action_addr_display}")
Esempio n. 25
0
async def _invite_user(config, device, invited_user_id, admin):
    action_addr = BackendOrganizationClaimUserAddr.build(
        organization_addr=device.organization_addr, user_id=invited_user_id)
    token = generate_invitation_token()

    action_addr_display = click.style(action_addr.to_url(), fg="yellow")
    token_display = click.style(token, fg="yellow")
    click.echo(f"url: {action_addr_display}")
    click.echo(f"token: {token_display}")

    async with spinner("Waiting for invitation reply"):
        invite_device_id = await invite_and_create_user(
            device=device,
            user_id=invited_user_id,
            token=token,
            is_admin=admin,
            keepalive=config.backend_connection_keepalive,
        )

    display_device = click.style(invite_device_id, fg="yellow")
    click.echo(f"Device {display_device} has been created")
async def _bootstrap_organization(config, addr, password, device_label, human_label, human_email):
    if not human_label:
        human_label = await aprompt("User fullname")
    if not human_email:
        human_email = await aprompt("User email")
    human_handle = HumanHandle(email=human_email, label=human_label)
    if not device_label:
        device_label = await aprompt("Device label", default=platform.node())

    async with apiv1_backend_anonymous_cmds_factory(addr=addr) as cmds:
        async with spinner("Bootstrapping organization in the backend"):
            new_device = await do_bootstrap_organization(
                cmds=cmds, human_handle=human_handle, device_label=device_label
            )

        device_display = click.style(new_device.slughash, fg="yellow")
        # We don't have to worry about overwritting an existing keyfile
        # given their names are base on the device's slughash which is intended
        # to be globally unique.
        with operation(f"Saving device {device_display}"):
            save_device_with_password(
                config_dir=config.config_dir, device=new_device, password=password
            )
Esempio n. 27
0
async def _invite_device(config, device, new_device_name):
    action_addr = BackendOrganizationClaimDeviceAddr.build(
        organization_addr=device.organization_addr,
        device_id=DeviceID(f"{device.user_id}@{new_device_name}"),
    )
    token = generate_invitation_token()

    action_addr_display = click.style(action_addr.to_url(), fg="yellow")
    token_display = click.style(token, fg="yellow")
    click.echo(f"url: {action_addr_display}")
    click.echo(f"token: {token_display}")

    async with spinner("Waiting for invitation reply"):
        await invite_and_create_device(
            device=device,
            new_device_name=new_device_name,
            token=token,
            keepalive=config.backend_connection_keepalive,
        )

    display_device = click.style(f"{device.device_name}@{new_device_name}",
                                 fg="yellow")
    click.echo(f"Device {display_device} is ready !")
Esempio n. 28
0
async def _claim_invitation(
    config: CoreConfig, addr: BackendInvitationAddr, save_device_with_selected_auth: Callable
) -> None:
    async with backend_invited_cmds_factory(
        addr=addr, keepalive=config.backend_connection_keepalive
    ) as cmds:
        try:
            async with spinner("Retrieving invitation info"):
                initial_ctx = await claimer_retrieve_info(cmds)
        except BackendConnectionRefused:
            raise RuntimeError("Invitation not found")

        if initial_ctx.greeter_human_handle:
            display_greeter = click.style(str(initial_ctx.greeter_human_handle), fg="yellow")
        else:
            display_greeter = click.style(initial_ctx.greeter_user_id, fg="yellow")
        click.echo(f"Invitation greeter: {display_greeter}")
        while True:
            try:
                if isinstance(initial_ctx, DeviceClaimInitialCtx):
                    new_device = await _do_claim_device(initial_ctx)
                else:
                    assert isinstance(initial_ctx, UserClaimInitialCtx)
                    new_device = await _do_claim_user(initial_ctx)
                if new_device:
                    break
            except InviteError as exc:
                click.secho(str(exc), fg="red")
            click.secho("Restarting the invitation process", fg="red")

        # Claiming a user means we are it first device, hence we know there
        # is no existing user manifest (hence our placeholder is non-speculative)
        if addr.invitation_type == InvitationType.USER:
            await user_storage_non_speculative_init(
                data_base_dir=config.data_base_dir, device=new_device
            )
        await save_device_with_selected_auth(config_dir=config.config_dir, device=new_device)
Esempio n. 29
0
async def _pki_enrollment_poll(
    config: CoreConfig,
    enrollment_id_filter: Optional[str],
    dry_run: bool,
    save_device_with_selected_auth: Callable,
    finalize: Sequence[str],
):
    pendings = PkiEnrollmentSubmitterSubmittedCtx.list_from_disk(config_dir=config.config_dir)

    # Try to shorten the UUIDs to make it easier to work with
    enrollment_ids = [e.enrollment_id for e in pendings]
    for enrollment_id_len in range(3, 64):
        if len({h.hex[:enrollment_id_len] for h in enrollment_ids}) == len(enrollment_ids):
            break

    # Manage pre-selected actions
    preselected_actions = {x: "finalize" for x in finalize}

    def _preselected_actions_lookup(enrollment_id: UUID) -> Optional[str]:
        for preselected in preselected_actions:
            if len(preselected) < enrollment_id_len:
                continue
            if enrollment_id.hex.startswith(preselected):
                return preselected_actions.pop(preselected)

    # Filter if needed
    if enrollment_id_filter:
        if len(enrollment_id_filter) < enrollment_id_len:
            raise RuntimeError()
        pendings = [e for e in pendings if e.enrollment_id.hex.starswith(enrollment_id_filter)]
        if not pendings:
            raise RuntimeError(f"No enrollment with id {enrollment_id_filter} locally available")

    def _display_pending_enrollment(pending: PkiEnrollmentSubmitterSubmittedCtx):
        enrollment_id_display = click.style(
            pending.enrollment_id.hex[:enrollment_id_len], fg="green"
        )
        display = f"Pending enrollment {enrollment_id_display}"
        display += f"\n  Submitted on: " + click.style(pending.submitted_on, fg="yellow")
        display += f"\n  Organization URL: " + click.style(pending.addr, fg="yellow")
        display += f"\n  Certificate SHA1 Fingerprint: " + click.style(
            pending.x509_certificate.certificate_sha1.hex(), fg="yellow"
        )
        display += "\n  Certificate Issuer Common Name: " + click.style(
            pending.x509_certificate.issuer_common_name, fg="yellow"
        )
        display += "\n  Certificate Subject Common Name: " + click.style(
            pending.x509_certificate.subject_common_name, fg="yellow"
        )
        display += "\n  Certificate Subject Email Address: " + click.style(
            pending.x509_certificate.subject_email_address, fg="yellow"
        )
        display += "\n  Requested Device Label: " + click.style(
            pending.submit_payload.requested_device_label, fg="yellow"
        )
        return display

    def _display_accepted_enrollment(accepted: PkiEnrollmentSubmitterAcceptedStatusCtx):
        display = (
            f"Enrollment has been accepted on " + click.style(ctx.accepted_on, fg="yellow") + " by:"
        )
        display += f"\n  Certificate SHA1 Fingerprint: " + click.style(
            accepted.accepter_x509_certificate.certificate_sha1.hex(), fg="yellow"
        )
        display += "\n  Certificate Issuer Common Name: " + click.style(
            accepted.accepter_x509_certificate.issuer_common_name, fg="yellow"
        )
        display += "\n  Certificate Subject Common Name: " + click.style(
            accepted.accepter_x509_certificate.subject_common_name, fg="yellow"
        )
        display += "\n  Certificate Subject Email Address: " + click.style(
            accepted.accepter_x509_certificate.subject_email_address, fg="yellow"
        )
        return display

    num_pendings_display = click.style(str(len(pendings)), fg="green")
    click.echo(f"Found {num_pendings_display} pending enrollment(s):")

    for ctx in pendings:
        click.echo(_display_pending_enrollment(ctx))

        async with spinner("Fetching PKI enrollment status from the backend"):
            try:
                ctx = await ctx.poll(extra_trust_roots=config.pki_extra_trust_roots)

            except Exception:
                # TODO: exception handling !
                raise

        if isinstance(ctx, PkiEnrollmentSubmitterSubmittedStatusCtx):
            # Nothing to do
            click.echo("Enrollment is still pending")

        elif isinstance(ctx, PkiEnrollmentSubmitterCancelledStatusCtx):
            click.echo(
                "Enrollment has been cancelled on " + click.style(ctx.submitted_on, fg="yellow")
            )
            if not dry_run:
                await ctx.remove_from_disk()

        elif isinstance(ctx, PkiEnrollmentSubmitterRejectedStatusCtx):
            click.echo(
                "Enrollment has been rejected on " + click.style(ctx.rejected_on, fg="yellow")
            )
            if not dry_run:
                await ctx.remove_from_disk()

        elif isinstance(ctx, PkiEnrollmentSubmitterAcceptedStatusButBadSignatureCtx):
            click.echo(
                "Enrollment has been accepted on " + click.style(ctx.accepted_on, fg="yellow")
            )
            raise RuntimeError(
                f"Cannot validate accept information with selected X509 certificate: {ctx.error}"
            )

        else:
            assert isinstance(ctx, PkiEnrollmentSubmitterAcceptedStatusCtx)
            click.echo(_display_accepted_enrollment(ctx))
            if not dry_run:
                preselected_finalize = _preselected_actions_lookup(ctx.enrollment_id) == "finalize"
                if not preselected_finalize and not await aconfirm("Finalize device creation"):
                    return
                ctx = await ctx.finalize()
                await save_device_with_smartcard_in_config(
                    config.config_dir,
                    ctx.new_device,
                    certificate_id=ctx.x509_certificate.certificate_id,
                    certificate_sha1=ctx.x509_certificate.certificate_sha1,
                )
                await ctx.remove_from_disk()
Esempio n. 30
0
async def _pki_enrollment_review_pendings(
    config: CoreConfig,
    device: LocalDevice,
    list_only: bool,
    accept: Sequence[str],
    reject: Sequence[str],
):
    # Connect to the backend
    async with backend_authenticated_cmds_factory(
        addr=device.organization_addr,
        device_id=device.device_id,
        signing_key=device.signing_key,
        keepalive=config.backend_connection_keepalive,
    ) as cmds:
        pendings = await accepter_list_submitted_from_backend(
            cmds=cmds, extra_trust_roots=config.pki_extra_trust_roots
        )
        num_pendings_display = click.style(str(len(pendings)), fg="green")
        click.echo(f"Found {num_pendings_display} pending enrollment(s):")

        # Try to shorten the UUIDs to make it easier to work with
        enrollment_ids = [e.enrollment_id for e in pendings]
        for enrollment_id_len in range(3, 64):
            if len({h.hex[:enrollment_id_len] for h in enrollment_ids}) == len(enrollment_ids):
                break

        preselected_actions = {**{x: "accept" for x in accept}, **{x: "reject" for x in reject}}

        def _preselected_actions_lookup(enrollment_id: UUID) -> Optional[str]:
            for preselected in preselected_actions:
                if len(preselected) < enrollment_id_len:
                    continue
                if enrollment_id.hex.startswith(preselected):
                    return preselected_actions.pop(preselected)

        def _display_pending_enrollment(pending):
            enrollment_id_display = click.style(
                pending.enrollment_id.hex[:enrollment_id_len], fg="green"
            )
            display = f"Pending enrollment {enrollment_id_display}"
            display += f"\n  Submitted on: " + click.style(pending.submitted_on, fg="yellow")
            display += f"\n  Certificate SHA1 fingerprint: " + click.style(
                pending.submitter_x509_certificate_sha1.hex(), fg="yellow"
            )
            if isinstance(pending, PkiEnrollementAccepterInvalidSubmittedCtx):
                display += click.style("\nInvalid enrollment", fg="red") + f": {pending.error}"
            else:
                assert isinstance(pending, PkiEnrollementAccepterValidSubmittedCtx)
                display += "\n  Certificate Issuer Common Name: " + click.style(
                    pending.submitter_x509_certificate.issuer_common_name, fg="yellow"
                )
                display += "\n  Certificate Subject Common Name: " + click.style(
                    pending.submitter_x509_certificate.subject_common_name, fg="yellow"
                )
                display += "\n  Certificate Subject Email Address: " + click.style(
                    pending.submitter_x509_certificate.subject_email_address, fg="yellow"
                )
                display += "\n  Requested Device Label: " + click.style(
                    pending.submit_payload.requested_device_label, fg="yellow"
                )
            return display

        for pending in pendings:
            enrollment_id = pending.enrollment_id
            click.echo(_display_pending_enrollment(pending))
            if list_only:
                continue

            if isinstance(pending, PkiEnrollementAccepterInvalidSubmittedCtx):
                action = _preselected_actions_lookup(enrollment_id)
                if action == "accept":
                    raise RuntimeError(f"Could not accept invalid enrollment {enrollment_id.hex}")

                elif action is None:
                    # Let the user pick and action
                    action = await aprompt(
                        "-> Action",
                        default="Ignore",
                        type=click.Choice(["Ignore", "Reject"], case_sensitive=False),
                    )
                    action = action.lower()

                if action == "ignore":
                    continue

                else:
                    # Reject the request
                    assert action == "reject"
                    async with spinner("Rejecting PKI enrollment from the backend"):
                        await pending.reject()
                    continue
            else:
                assert isinstance(pending, PkiEnrollementAccepterValidSubmittedCtx)

                action = _preselected_actions_lookup(enrollment_id)
                if action is None:
                    # Let the user pick and action
                    action = await aprompt(
                        "-> Action",
                        default="Ignore",
                        type=click.Choice(["Ignore", "Reject", "Accept"], case_sensitive=False),
                    )
                    action = action.lower()

                if action == "ignore":
                    continue

                # Reject the request
                if action == "reject":
                    async with spinner("Rejecting PKI enrollment from the backend"):
                        await pending.reject()
                    continue

                else:
                    # Accept the request
                    assert action == "accept"

                    # Let the admin edit the user information
                    granted_device_label, granted_human_handle, granted_profile = await ask_info_new_user(
                        default_device_label=pending.submit_payload.requested_device_label,
                        default_user_label=pending.submitter_x509_certificate.subject_common_name,
                        default_user_email=pending.submitter_x509_certificate.subject_email_address,
                    )

                    async with spinner("Accepting PKI enrollment in the backend"):
                        await pending.accept(
                            author=device,
                            device_label=granted_device_label,
                            human_handle=granted_human_handle,
                            profile=granted_profile,
                        )

        if preselected_actions:
            raise RuntimeError(
                f"Additional --accept/--reject elements not used: {', '.join(preselected_actions.keys())}"
            )