Automating EntraID onboarding in Veeam

Disclaimer:
I’m not a professional developer, and this script was created on a beta build of the software. It is intended to demonstrate the “art of the possible” rather than serve as production-ready code. Use this example with caution, and test thoroughly in your environment before relying on it for critical tasks. Please see the Gitlab repo for the full subset of code as these are just snippets to show the process.

Introduction

As organizations continue to scale, automation becomes essential for managing complex infrastructures efficiently. With the release of version 12.3 of Veeam Backup & Replication, a set of new APIs for EntraID is available. These APIs simplify onboarding EntraID customers, allowing service providers to automate tenant creation and streamline setup processes.

Directory Structure

Here’s the layout of my project directory:

.
├── README.md
├── device_code.py
├── entra.py
├── list_repos.py
├── main.py
├── requirements.txt
├── veeamAuth.py
└── .env

Workflow

  1. Authenticate with the Veeam Backup and Replication (VBR) Server
    Obtain a bearer token using the /api/oauth2/token endpoint.

  2. Retrieve a List of Repositories
    Access the list of repositories via /api/v1/backupInfrastructure/repositories. This step is essential to get the ID of the required repository for caching.

  3. Generate a Device Code
    Use the /api/v1/cloudCredentials/appRegistration endpoint to obtain a device code, which will enable user authentication with Microsoft.

  4. Authenticate with Microsoft using the Device Code
    The customer must authenticate using the provided device code to authorize Veeam’s access.

  5. Create an EntraID Tenant in VBR
    Complete the process by creating an EntraID tenant in VBR via the /api/v1/inventory/entraId/tenants endpoint.

Note: As you may have noticed, this workflow requires a manual step in the middle: much like the process for authenticating Microsoft 365, we need to authorize Veeam to create the necessary app registrations. While this step could potentially be integrated into a custom portal for service providers, for now, the process involves opening a browser tab to complete the authentication.

Step 1

First, we need to authenticate with the Veeam Backup and Replication server. We can achieve this by obtaining a bearer token via the /api/oauth2/token endpoint, passing through a username and password.

def authenticate(veeam_server, username, password, api_version):
    """Authenticate to Veeam server and return session token."""
    url = f"{veeam_server}/api/oauth2/token"
    headers = {"Content-Type": "application/x-www-form-urlencoded", "x-api-version": api_version}
    data = {"grant_type": "password", "username": username, "password": password}

    response = requests.post(url, headers=headers, data=data, verify=False)
    if response.status_code == 200:
        return response.json().get('access_token')
    else:
        print(f"Authentication failed: {response.status_code} - {response.text}")
        return None

Step 2

In this step, we need to retrieve a list of repositories and grab the repository ID, which is required for the Entra API call.

# Function to list repositories by calling the API
def list_repos(veeam_server, api_version, token):
    url = f"{veeam_server}/api/v1/backupInfrastructure/repositories"
    headers = {
        "Content-Type": "application/x-www-form-urlencoded",
        "x-api-version": api_version,
        "Authorization": f"bearer {token}"
    }
    
    try:
        response = requests.get(url, headers=headers, verify=False)
        response.raise_for_status()
        
        # Debugging: Print status and content to ensure we’re getting a valid response
        print("Status Code:", response.status_code)
        print("Response Content:", response.text)
        
        # Parse and return repository data correctly
        repos_data = response.json().get('data', [])
        return [{"id": repo['id'], "name": repo['name']} for repo in repos_data]
        
    except requests.exceptions.RequestException as e:
        print(f"Request error: {e}")
        return []

Step 3

In this step, we need to generate a device code. This part can be a bit confusing because, while we aim to automate the entire process, full automation isn’t strictly possible. We require the customer to authenticate themselves to grant Veeam permission to create the necessary app registration.

By generating a device code, we enable the customer to authenticate manually, ensuring they provide the needed authorization for the process to proceed.

def get_devicecode(veeam_server, api_version, token):
    url = f"{veeam_server}/api/v1/cloudCredentials/appRegistration"
    data = {"type": "AzureCompute", "region": "Global"}
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "x-api-version": api_version,
        "Authorization": f"bearer {token}"
    }

    print("getting device code")
    print(url)
    print(data)
    print(headers)
    try:
        response = requests.post(url, json=data, headers=headers, verify=False)
        response.raise_for_status()
        json_data = response.json()
        print(json_data)
        
        if "verificationCode" in json_data:
            return json_data["verificationCode"]
        else:
            print("Error: 'verificationCode' not found.")
            return None

    except requests.exceptions.RequestException as e:
        print(f"Request error: {e}")
        return None

Step 4

Now that we have everything we need, we can pull it all together in the EntraID API call to create the tenant.

In this step, we are performing two main actions:

  1. Authentication
    Since the customer needs to authenticate, we will open a browser session to the Microsoft device login page and provide them with a device login code. The customer will enter this code to validate their authentication.

  2. API Call to VBR Server
    Next, we will make the required API call to the VBR server. This call will remain in a waiting state for a set period until it receives authorization confirmation from Microsoft. This is why the first step is necessary: after initiating the API call, it will wait until the customer has successfully authenticated. Once authentication is confirmed, the API call will complete successfully, adding the EntraID tenant.

def add_entraIDTenant(tenantId, veeam_server, api_version, token, cache_repo_id, verification_code):
    """
    Adds an Entra ID tenant to the Veeam backup server.

    Parameters:
        tenantId (str): The tenant ID to be added.
        veeam_server (str): URL of the Veeam server.
        api_version (str): API version for the request.
        token (str): Authorization token for Veeam server.
        cache_repo_id (str): ID of the repository to be used.
        verification_code (str): Device code provided by the user after authentication.

    Returns:
        response (requests.Response): Response object from the POST request.
    """
    url = f"{veeam_server}/api/v1/inventory/entraId/tenants"
    headers = {
        "Content-Type": "application/json",
        "x-api-version": api_version,
        "Authorization": f"bearer {token}"
    }
    payload = {
        "tenantId": tenantId,
        "description": "Created by Python",
        "cacheRepositoryId": cache_repo_id,
        "region": "Global",
        "creationMode": "newAccount",
        "newAccount": {"verificationCode": verification_code}
    }

    try:
        response = requests.post(url, headers=headers, json=payload, verify=False)
        print("Response Status Code:", response.status_code)
        print("Response Content:", response.text)
        return response  # Return the response object for status checking
    except requests.RequestException as e:
        print(f"Request error: {e}")
        return None  # Return None if there’s an exception

Fantastic! After running the script and successfully authenticating with the device code, we have a successful entry in VBR.

Conclusion

Automating EntraID onboarding with Veeam’s API simplifies the onboarding process and can save significant time, especially for large organizations with frequent tenant provisioning needs. By leveraging this Python script, administrators can streamline the process, reducing the need for repetitive setup tasks and manual interventions. While this guide outlines a fundamental approach, there’s plenty of room for customization to suit specific requirements.

Hopefully, this provides a solid foundation to build from! I hope you found this guide helpful. As always, keep on learning!