Monday, May 30, 2011

Git with ssh on Windows

While Git is a great source control system that can bend to almost any source control workflow you might have, support on windows varies from awesome (Git Extensions, msysgit) to downright awful (using git, ssh, or http protocol). I've struggled enough with setting up an ssh server on windows to host a central repository to warrant documenting it.

First you will need to install Cygwin with the openssh and git modules enabled.

After the installation we'll need to let cygwin know about the domain accounts we want to use. For this we'll use the mkpasswd command. For domain accounts use the '-d' option.

$ mkpasswd -d -u username >> /etc/passwd

This will append the entry to the /etc/passwd file. Do this for all of the accounts you want to add. You may need to subsequently edit the /etc/passwd file and set the appropriate home directory to /home/username especially if your company likes to have a network home drive for all of their users.

Now it's time to create a new bare repository.

$ git init --bare myrepo.git
$ git config core.sharedRepository group

The second command lets git know that it should use group write permissions when creating files and directories. We'll also want to set the group permissions to special so that newly created files and folders have the same group permissions.

$ chmod -R g+ws .

Now that your repository is set up. Let's setup the ssh server.

$ ssh-host-config

The script will begin by generating host key files and config files. It will then prompt you to use privilege separation. Answer yes and again when it prompts you to create an unprivileged user.

Next the script will ask if you want to install sshd as a service. Answer yes. The next question will tell you that you need a privileged account to run the service and if you want to use the name 'cyg_server'. Either say no, or if you already have an appropriate privileged account answer yes and enter the name you want to use. If the account you entered does not have the appropriate permissions the script will warn you. I recommend letting the script create a 'cyg_server' account. When prompted enter an appropriate password that conforms to your password policies.

Finally the script will ask you to enter the values for the CYGWIN environment variable. Enter 'tty acl'. This will let you use windows security to access the file system and insert appropriate characters.

After all of that, ssh will be configured so we just need to start it.

$ cygrunsrv --start sshd

Alternatively you can go into the services list, find CYGWIN sshd and start it there.
If you start seeing errors at this point there is likely something wrong with the password you set up for the 'cyg_server' account.

As a final step you will likely want to create a symlink to your repository in the root folder. This will allow the ssh URI to be more concise and friendly.

$ ln -s /path/to/your/repo /repo

Once you have the ssh server running, it's time to connect to it. Let's push our local repository to the remote.

$ git push ssh://server/repo

If you did not set up a symlink your address will need to contain the appropriate cygwin path to your repo starting from /. The first time you connect it will prompt you to add the host key to your known_hosts file. Finally you will be prompted you to enter your password. At this point you have everything in place to access the repository remotely.

As a final step you can set up public/private keys in order to identify yourself to the ssh server. This will allow you to forgo having to enter your password. First, set up your ssh keys

$ ssh-keygen -t rsa

This will create 2 files in your ~/.ssh drive, one is your public key (id_rsa.pub) and the other is your private key (id_rsa). The process will involve you copying the contents of your public key to a /home/username/.ssh/authorized_keys file on the server. One way of doing this is to create a share to your home drive on the server that only your account can access, then you can create an authorized_keys file and copy the contents in notepad.

Git can still be a pain to work with on Windows, but hopefully this will make your life a little easier.

Update:
If you would rather set up git via http protocol. Have a look at this post if you want to go via Apache. Or for an IIS solution check this out.

Wednesday, May 4, 2011

Custom properties for IPrincipal

Sometimes you need to provide application specific values that are associated with a particular user. Let's say your application must handle role based security by using AD groups. Furthermore let's say that you wanted those group mappings to be configurable. A way to solve this would be to create your own implementation of IPrincipal. It might look something like this:

public class ApplicationPrincipal : IPrincipal
{
    private readonly IIdentity _identity;
    private readonly IPrincipal _principal;
    private readonly ISecurityConfiguration _config;

    public class ApplicationPrincipal(IIdentity identity, IPrincipal principal, ISecurityConfiguration config)
    {
        _identity = identity;
        _principal = principal;
        _config = config;
    }

    public IIdentity Identity { get { return _identity; } }

    public bool IsInRole(string role)
    {
        var windowsGroup = MapInternalRoleToWindowsGroup(role);
        return _principla.IsInRole(windowsGroup);
    }

    public string MapInternalRoleToWindowsGroup(string role)
    {
        switch (role)
        {
            case "Admin": return _config.AdminGroup;
            case "User": return _config.UserGroup;
            default: throw new SecurityException("Invalid group name: " + role);
        }
    }
}

Now that we have our own principal implementation we need a way to let the application access it. A good place to do that would be in an HttpModule that handles the PostAuthenticateRequest event.

public class ApplicationPrincipalModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PostAuthenticateRequest += PostAuthenticateRequest;
    }

    public void Dispose()
    {
        context.PostAuthenticateRequest -= PostAuthenticateRequest;
    }

    private void PostAuthenticateRequest(object sender, EventArgs e)
    {
        var httpContext = HttpContext.Current;
        var principal = new ApplicationPrincipal(
                             httpContext.User.Identity, httpContext.User, 
                             new SecurityConfiguration());
        httpContext.User = principal;
    }
}

Once a user has been authenticated, by whichever method your application is set up to authenticate, this module will set the HttpContext.Current.User to your implementation of IPrincipal. This can then be referenced where needed in your application.