Python clone a private GitHub repo via SSH authentication without access to ssh binaries (under Azure Functions)

115 Views Asked by At

Our team needed to clone a private GitHub repo within an Azure Function, where you can only run Python code - but there is no access to the shell or pre-installed Git or SSH binaries. At the same time, we can't use PAT token authentication for GitHub's HTTPS API - only SSH authentication (key-based) is allowed.

Note that the repository needed to be cloned for some metadata and config files in it - not for the function code or Python packages, so the requirements.txt is not what we were looking for.

How can this be done?

The GitPython package supports SSH key-based authentication but relies on system SSH binaries to achieve that. We have not found a way to make it work under Azure Functions environment.

P.S> The question is posted with the self-answer (for solution documentation). See the answer below.

1

There are 1 best solutions below

0
Dmitry Alergant On

We found the following two projects which are both pure Python based:

https://dulwich.io/ (Python Git package) and https://www.paramiko.org/ (Python SSH package)

There was evidence that they could work together (though authors say they consider this support to be "experimental" and not covered by tests), and an end-to-end code sample was hard to come by.

After some back-end-forth, the following snippet worked:

    from paramiko import  RSAKey
    import os
    import dulwich
    from dulwich import porcelain
    from dulwich.contrib.paramiko_vendor import ParamikoSSHVendor
    
    #Constants
    private_key_path = "~.ssh/id_rsa_githubrepo"
    repo_url = "[email protected]:myorg/myrepo.git"
    local_path = "/tmp/repo"
    
    # Load private key for SSH
    private_key = RSAKey(filename=private_key_path)
    ssh_kwargs = {"pkey": private_key}
    
    def get_dulwich_ssh_vendor():
        vendor = ParamikoSSHVendor(**ssh_kwargs)
        return vendor
    
    # overriding another module's method: gross! But there does not seem to be another way
    dulwich.client.get_ssh_vendor = get_dulwich_ssh_vendor

    # cloning
    repo = porcelain.clone(repo_url, target=local_path, checkout=True)

    #validation
    with open(os.path.join(local_path, "README.md"), "r") as f:
        print(f.read())
    ```