How To - Use connection info for Domain Join

Overview

In this how to we will cover how to modify your domain join action to use a connection info back to your domain. This will remove the need to create multiple parameters on your blueprint and to have username, passwords and domains listed.

Considerations

  • Have an understand of Python

  • Have admin access to create connection info

  • Know the credentials to the domain to enable the connection

Procedure

Creating the connection info

  1. Logged in as an administrator, go to admin → External Systems (1) → Connection Info (2)

  2. Click on New Connection Info

  3. On the pop out, provide a Name, IP/HostName, Username, Password at a minimum and click Create
    NOTE: You can place the host as a single domain controller, or, the complete domain.

  4. You will now see your new connection info created

Amending the Join OU in AD Domain Action

NOTE: Please backup the code or copy the code into a new action before modifying incase you need to role back

  1. Go to Admin → Orchestration (1) → Actions (2)

  2. Locate the Join OU in AD Domain Action and click on the name

  3. The code you will change is below. Effectively we are doing the following:

    Importing ConnectionInfo
    Defining ConnectionInfo
    Defining the new domain, ou, username and password
    Doing validation against these new parameters

    #!/usr/local/bin/python
    
    from __future__ import print_function
    import sys
    from common.methods import set_progress
    import re
    from utilities.models import ConnectionInfo
    
    ci = ConnectionInfo.objects.get(name="Active Directory SovLabs")
    
    """
    Example Hook to join a Windows server to a particular OU of an AD domain.
    
    You will need to create a connection info to the domain you require.  
    Where you would put the IP/Hostname you MUST put the domain name
    This will help to connect to the first responding domain controller
    and also will be the domain that you will join this Virtual Machine too.
    
    You will need to ensure one additional parameter is available to your Blueprint which is named OU
    This is required to pass through the OU structure for where to place the VM
    Example: ou=prod,ou=servers,ou=Corporate,dc=mydomain,dc=com
    
    Additional parameters: In order to run the necessary script on the server,
    CloudBolt must know the username and password with which to log into the
    template. By default, CloudBolt uses the Administrator username. The password
    can be set using either the 'Windows Server Password' or 'VMware Template Password'
    parameters. If the username on the template is not Administrator, use the
    'Server Username' parameter.
    
    This hook will be especially useful as a post-provisioning hook.
    
    Note: If the server is already in the specified domain (even if it is not in the
    requested OU), the script will not change anything and will print a message
    saying the server cannot be added to the domain again.
    
    Note: The password provided will be temporarily stored in plain text in a PowerShell script
    file on the system, but the file will be deleted when the task is complete.
    
    Testing this hook: To speed up the development cycle of testing this hook, it
    can be run from the command line. Using a completed server provision job, run
    `./join_domain_ou.py <job id>`. The job's originating order must have had the
    above parameters already set.
    """
    
    
    def run(job, logger):
        # If the job this is associated with fails, don't do anything
        if job.status == "FAILURE":
            return "", "", ""
    
        server = job.server_set.last()
        if not server.is_windows():
            msg = "Skipping joining domain for non-windows VM"
            return "", msg, ""
        if not server.resource_handler.cast().can_run_scripts_on_servers:
            logger.info("Skipping hook, cannot run scripts on guest")
            return "", "", ""
    
        set_progress("Joining OU in Domain based on parameters")
    
        ciou = server.get_value_for_custom_field("ou")
        ciusername = ci.username
        cipassword = ci.password
        cidomain = ci.ip
    
        fail_msg = "Parameter '{}' not set, cannot run hook"
    
        # confirm that all custom fields have been set
        if not cidomain:
            msg = fail_msg.format("cidomain")
            return "FAILURE", msg, ""
        elif not ciou:
            msg = fail_msg.format("ciou")
            return "FAILURE", msg, ""
        elif not ciusername:
            msg = fail_msg.format("ciusername")
            return "FAILURE", msg, ""
        elif not cipassword:
            msg = fail_msg.format("cipassword")
            return "FAILURE", msg, ""
    
        script = [
            "Add-Computer ",
            '-DomainName "{}" '.format(cidomain.strip()),
            '-OUPath "{}" '.format(ciou.strip()),
            "-Credential ",
            "(",
            "New-Object ",
            "System.Management.Automation.PSCredential (",
            '"{}", '.format(ciusername.strip()),
            '(ConvertTo-SecureString "{}" -AsPlainText -Force)'.format(cipassword.strip()),
            ")) ",
        ]
    
        script = "".join(script)
    
        # For debugging. We'll eventually put this in the code base directly.
        username = server.get_credentials()["username"]
        msg = "Executing script on server using username '{}'".format(username)
        logger.info(msg)
    
        try:
            output = server.execute_script(script_contents=script)
            logger.info("Script returned output: {}".format(output))
        except RuntimeError as err:
            set_progress(str(err))
            # If the error is due to already being in the domain, it's OK
            errmsg = re.sub(r"\r", "", str(err))
            errmsg = re.sub(r"\n", "", errmsg)
            found = re.search(r"because it is already in that domain", errmsg)
            if not found:
                return "FAILURE", str(err), ""
    
        return "", "", ""
    
    
    if __name__ == "__main__":
        from jobs.models import Job
        from utilities.logger import ThreadLogger
    
        logger = ThreadLogger(__name__)
        job_id = sys.argv[1]
        job = Job.objects.get(id=job_id)
    
        print(run(job, logger))


  4. Save your code and do some testing.

    NOTE: You can modify this in any form you require, you could eventually change the ci = value to be dynamic to select from the blueprint so you could select a domain, and that domain could be a part of the ConnectionInfo name.

    For Example:
    Drop down of domain.bcd , domaine.fgh, domaini.jkl
    Your connection info names could be Active Directory domaina.bcd and so forth for each domain.
    Then in your script you amend to be something like:

    domainselection = "Active Directory {}".format(server.domainselect)
    ci = ConnectionInfo.objects.get(name=domainselection)


    This would then look for a parameter on your blueprint called domainselect, and put the domain into the connection info string so you could make multiple different domain connections with the one script.

Additional information

CloudBolt blueprint actions document : https://docs.cloudbolt.io/articles/#!cloudbolt-latest-docs/blueprint-actions
CloudBolt action document : https://docs.cloudbolt.io/articles/#!cloudbolt-latest-docs/actions

Have more questions? Submit a request

0 Comments

Please sign in to leave a comment.