Skip to content

Migrating Users Installation

It is possible to migrate a Bizzkit Users configuration from one instance to another. This could, for example, be done to migrate a customer from an on-prem solution to a Bizzkit cloud solution.

However it may also be a good time to review the organizations security setup. Setting up the new environment from the ground instead of migrating existing data could be serve as a healthy checkup.

This guide will assume that two instances of Bizzkit Users are available, an old source instance, and a new destination instance. It also assumes that the migrator is authorized in the Auth API.

Note

Username/password authentication is not possible in Bizzkit Users instance in the cloud. Client credentials are necessary to configure a Bizzkit Users instance.

Migrating users and roles through the API

To migrate an instance of Bizzkit User Management, several different entities have to be moved. This section walks through an example of C# code for migrating from a source IIamClient to a destination IIamClient.

At the top level, a method is called for migrating each type of data, which has to be moved.

public static async Task MigrateBizzkitUsersAsync(IIamClient sourceClient, IIamClient destinationClient)
{
    await MigrateUsersAsync(sourceClient, destinationClient);
    await MigrateRolesAsync(sourceClient, destinationClient);
    await MigrateGrantsAsync(sourceClient, destinationClient);
    await MigrateProtectedApisAsync(sourceClient, destinationClient);
    await MigrateMachineClientsAsync(sourceClient, destinationClient);
    await MigrateInteractiveClientsAsync(sourceClient, destinationClient);
    await MigrateClientsideApplicationsAsync(sourceClient, destinationClient);
}

Migrating users

Firstly, users are migrated by iterating over each user in the source instance and creating it in the destination instance.

private static async Task MigrateUsersAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient)
{
    var users = await sourceClient.ListUsersAsync();

    foreach (var user in users)
    {
        await destinationClient.CreateUserAsync(new CreateUserModel
        {
            Email = user.Email,
            FullName = user.FullName
        });
    }
}

Migrating roles

After migrating users, roles can be migrated, also adding the roles to the appropriate users, based on the configuration of the source instance.

private static async Task MigrateRolesAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient)
{
    var roles = await sourceClient.ListRolesAsync();

    foreach (var role in roles)
    {
        await destinationClient.CreateRoleAsync(new CreateRoleModel
        {
            Name = role.Name,
            Description = role.Description
        });

        await destinationClient.SetUsersInRoleAsync(role.Name, new SetRoleUsersModel
        {
            UserIds = role.RoleUsers.Select(x => x.Id)
        });
    }
}

Migrating grants

Grants are migrated by adding them to the appropriate roles, based on the configuration of the source instance.

private static async Task MigrateGrantsAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient)
{
    var grants = await sourceClient.ListGrantsAsync();

    foreach (var roleGrantPair in grants.GroupBy(x => x.RoleName))
    {
        await destinationClient.SetGrantsForRoleAsync(roleGrantPair.Key, new SetRoleGrantsModel
        {
            PermissionIds = roleGrantPair.Select(x => x.PermissionId)
        });
    }
}

Migrating downstream applications

Migrating downstream applications must be done with great care. Only import data that is actually needed. Do NOT migrate Bizzkit applications into the cloud.

Migrating protected APIs

Next, protected APIs are migrated by iterating over each protected API in the source instance and creating it in the destination instance.

private static async Task MigrateProtectedApisAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient)
{
    var protectedApis = await sourceClient.ListProtectedApisAsync();

    foreach (var protectedApi in protectedApis.Where(x=>ShouldBeMigrated(x)))
    {
        await destinationClient.CreateProtectedApiAsync(protectedApi,
            new CreateProtectedApiModel());
    }
}

Migrating machine clients

To migrate machine clients, it is necessary to register the client and create new secrets for the client.

private static async Task MigrateMachineClientsAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient)
{
    var machineClientIds = await sourceClient.ListMachineClientsAsync();

    foreach (var id in machineClientIds.Where(x=>ShouldBeMigrated(x))
    {
        await MigrateMachineClientAsync(sourceClient, destinationClient, id);
        await MigrateMachineClientSecretsAsync(sourceClient, destinationClient, id);
    }
}

The client itself is copied from the source instance to the destination instance.

private static async Task MigrateMachineClientAsync(
    IIamClient sourceClient,
    IIamClient destinationClient, 
    string id)
{
    var machineClient = await sourceClient.GetMachineClientByIdAsync(id);

    await destinationClient.CreateMachineClientAsync(id, new CreateMachineClientModel
    {
        Name = machineClient.Name,
        ClientSecret = null,
        Roles = machineClient.Roles,
        AllowedScopes = machineClient.AllowedScopes
    });
}

New secrets can then be created for the machine client. This code snippet will simply print out the newly created secret, so the application using the machine client can be updated with the new secret.

Note

Generally, a client should only have one secret, except for when performing rolling deployment of secrets.

private static async Task MigrateMachineClientSecretsAsync(
    IIamClient sourceClient,
    IIamClient destinationClient, string id)
{
    Console.WriteLine($"Creating secrets for machine client with id {id}:");

    var clientSecrets = await sourceClient.ListMachineClientSecretsByIdAsync(id);

    foreach (var clientSecret in clientSecrets)
    {
        var newSecret = await destinationClient.CreateMachineClientSecretAsync(id,
        new CreateClientSecretModel
        {
            Description = clientSecret.Description
        });

        Console.WriteLine("Created new client secret.");
        Console.WriteLine($"- description: {newSecret.Description}");
        Console.WriteLine($"- secret id: {newSecret.ClientSecretId}");
        Console.WriteLine($"- secret: {newSecret.Secret}");
    }

    Console.WriteLine();
}

Migrating interactive applications

Like machine clients, to migrate interactive applications, it is necessary to register the client and create new secrets for the client.

private static async Task MigrateInteractiveClientsAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient)
{
    var interactiveClients = await sourceClient.ListInteractiveApplicationsAsync();

    foreach (var id in interactiveClients.Where(x=>ShouldBeMigrated(x)))
    {
        await MigrateInteractiveClientAsync(sourceClient, destinationClient, id);
        await MigrateInteractiveClientSecretsAsync(sourceClient, destinationClient, id);
    }
}

The client itself is copied from the source instance to the destination instance.

private static async Task MigrateInteractiveClientAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient, 
    string id)
{
    var interactiveClient = await sourceClient.GetInteractiveApplicationByIdAsync(id);

    await destinationClient.CreateInteractiveApplicationAsync(id, new CreateInteractiveApplicationModel
    {
        Name = interactiveClient.Name,
        ClientSecret = null,
        Legacy = interactiveClient.Legacy,
        AllowedScopes = interactiveClient.AllowedScopes,
        RedirectUris = interactiveClient.RedirectUris,
        FrontChannelLogoutPath = interactiveClient.FrontChannelLogoutUri,
        PostLogoutRedirectPath = interactiveClient.PostLogoutRedirectUri
    });
}

New secrets can then be created for the interactive application. This code snippet will simply print out the newly created secret, so the application using the interactive application can be updated with the new secret.

Note

Generally, a client should only have one secret, except for when performing rolling deployment of secrets.

private static async Task MigrateInteractiveClientSecretsAsync(
    IIamClient sourceClient, 
    IIamClient destinationClient, 
    string id)
{
    Console.WriteLine($"Creating secrets for interactive client with id {id}:");

    var clientSecrets = await sourceClient.ListInteractiveApplicationSecretsByIdAsync(id);

    foreach (var clientSecret in clientSecrets)
    {
        var newSecret = await destinationClient.CreateInteractiveApplicationSecretAsync(id,
            new CreateClientSecretModel
            {
                Description = clientSecret.Description
            });

        Console.WriteLine("Created new client secret.");
        Console.WriteLine($"- description: {newSecret.Description}");
        Console.WriteLine($"- secret id: {newSecret.ClientSecretId}");
        Console.WriteLine($"- secret: {newSecret.Secret}");
    }

    Console.WriteLine();
}

Migrating Client side applications

Client side applications are migrated by iterating over each client side application in the source instance and creating it in the destination instance.

private static async Task MigrateClientsideApplicationsAsync(IIamClient sourceClient, IIamClient destinationClient)
{
    var clientsideApplicationIds = await sourceClient.ListClientSideApplicationsAsync();

    foreach (var id in clientsideApplicationIds.Where(x=>ShouldBeMigrated(x)))
    {
        var clientsideApplication = await sourceClient.GetClientSideApplicationByIdAsync(id);

        await destinationClient.CreateClientSideApplicationAsync(id, new CreateClientSideModel
        {
            Name = clientsideApplication.Name,
            Legacy = clientsideApplication.Legacy,
            BaseDomain = clientsideApplication.BaseUrl,
            AllowedScopes = clientsideApplication.AllowedScopes,
            RedirectUris = clientsideApplication.RedirectUris
        });
    }
}

Setting up new upstream app registrations

Lastly, it is necessary to register the new Bizzkit Users instance as a downstream client in the desired upstream provider.

Example

To register Bizzkit Users in AzureAD, simply go to https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app and follow the guide for configuring access.

After creating the credentials for the upstream provider, it can be registered in Bizzkit Users by using the appropriate API endpoint.

Example

To add the credentials for AzureAD to the Bizzkit Users instance through the SDK, the following C# snippet can be used.

private static async Task RegisterAzureAdAsync(IIamClient destinationClient)
{
    const string id = "my-azure-provider";

    await destinationClient.SetAzureAdUpstreamProviderAsync(id, new SetAzureAdAccountModel
    {
        TenantId = "tenant-id-from-azure",
        ClientId = "client-id-from-azure",
        ClientSecret = "client-id-from-azure",
        DisplayName = "AzureAD"
    });
}