diff --git a/README.mkd b/README.mkd index 5a59bd1..7cbbaf3 100644 --- a/README.mkd +++ b/README.mkd @@ -3,19 +3,22 @@ > [IMPORTANT: There is now an "upgrade" document in the "doc" directory; > please read if upgrading gitolite] +> [Update 2009-10-10: apart from all the nifty new features, there's now an +> "easy install" script in the src directory. Please see the INSTALL +> document in the doc directory for details] + ---- -Gitolite is the bare essentials of gitosis, with a completely different -config file that allows (at last!) access control down to the branch level, -including specifying who can and cannot *rewind* a given branch. It is -released under GPL v2. See COPYING for details. +Gitolite is a rewrite of gitosis, with a completely different config file that +allows (at last!) access control down to the branch level, including +specifying who can and cannot *rewind* a given branch. In this document: * why - * what's gone - * what's new - * the workflow + * what's extra + * security + * contact and license ---- @@ -28,30 +31,15 @@ a typical $DAYJOB setting, there are some issues: and be done * often, "python-setuptools" isn't installed (and on a Solaris9 I was trying to help remotely, we never did manage to install it eventually) - * or you don't have root access, or the ability to add users + * you don't have root access, or the ability to add users (this is also true + for people who have just one userid on a hosting provider) * the most requested feature (see "what's new?") had to be written anyway -### what's gone - -While I was pondering the need to finally learn python[1] , I also realised -that: - - * no one in $DAYJOB type environments will use or approve access methods - that work without any authentication, so I didn't need gitweb/daemon - support in the tool or in the config file. - - Update 2009-09-24: I don't use this feature but someone wanted it, so I - added it... see the "faq, tips, etc" document for more - - * the idea that you admin it by pushing to a special repo is nice, but not - really necessary because of how rarely these changes are made, especially - considering how much code is involved in that piece - All of this pointed to a rewrite. In perl, naturally :-) -### what's new +### what's extra -Per-branch permissions. You will not believe how often I am asked this at +**Per-branch permissions**. You will not believe how often I am asked this at $DAYJOB. This is almost the single reason I started *thinking* about rolling my own gitosis in the first place. @@ -61,50 +49,42 @@ deleting a branch (which is really just an extreme form of rewind). I needed something in between allowing anyone to do it (the default) and disabling it completely (`receive.denyNonFastForwards` or `receive.denyDeletes`). -Take a look at the example config file in the repo to see how I do this. I -copied the basic idea from `update-hook-example.txt` (it's one of the "howto"s -that come with the git source tree). However, please note the difference in -the size and complexity of the *operational code* between the update hook in -that example, and in mine :-) The reason is in the next section. +Here're **some more features**. All of them are documented in detail +somewhere in the `doc/` subdirectory. -### the workflow + * simpler, yet far more powerful, config file syntax, including specifying + gitweb/daemon access. You'll need this power if you manage lots of users + + repos + combinations of access + * config file syntax gets checked upfront, and much more thoroughly + * if your requirements are still too complex, you can split up the config + file and delegate authority over parts of it + * more comprehensive logging [aka: management does not think "blame" is just + a synonym for "annotate" :-)] + * "personal namespace" prefix for each dev + * migration guide and simple converter for gitosis conf file + * "exclude" (or "deny" rights in the config file) -- this is the "rebel" + branch in the repository, and always will be ;-) -In order to get per-branch access, you *must* use an update hook. However, -that only gets invoked on a push; "read" access still has to be controlled -right at the beginning, before git even enters the scene (just the way gitosis -currently works). +### security -So: either split the access control into two config files, or have two -completely different programs *both* parse the same one and pick what they -want. Crap... I definitely don't want the hook doing any parsing, (and it -would be nice if the auth-control program didn't have to either). +Due to the environment in which this was created and the need it fills, I +consider this a "security" program, albeit a very modest one. The code is +very small and easily reviewable -- the 2 programs that actually control +access when a user logs in total about 200 lines of code (about +80 lines according to "sloccount"). -So I changed the workflow completely: +For the first person to find a security hole in it, defined as allowing a +normal user (not the gitolite admin) to read a repo, or write/rewind a ref, +that the config file says he shouldn't, and caused by a bug in *code* that is +in the "master" branch, (not in the other branches, or the configuration file +or in Unix, perl, shell, etc.)... well I can't afford 1000 USD rewards like +djb, so you'll have to settle for 1000 INR (Indian Rupees) as a "token" prize +:-) - * all admin changes happen *on the server*, in a special directory that - contains the config and the users' pubkeys. But there's no commit and - push afterward - * instead, after making changes, you "compile" the configuration. This - refreshes `~/.ssh/authorized_keys`, as well as puts a parsed form of the - access list in a file for the other two pieces to use. +---- -The pre-parsed form is basically a huge perl variable. It's human readable -too (never mind what the python guys say!) +### contact and license -So the admin knows immediately if the config file had any problems, which is -good. Also, the relatively complex parse code is not part of the actual -access control points, which are: - - * the program that is run via `~/.ssh/authorized_keys` (I call it - `gl-auth-command`, equivalent to `gitosis-serve`); this decides whether - git should even be allowed to run (basic R/W/no access) - * the update-hook on each repo, which decides the per-branch permissions - -### footnotes - -[1] I hate whitespace to mean anything significant except for text; this is a -personal opinion *only*, so pythonistas please back off :-) - -### contact +Gitolite is released under GPL v2. See COPYING for details. sitaramc@gmail.com diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index ea80bec..b3b3361 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -5,7 +5,7 @@ # this file is meant to be pulled into a perl program using "do" or "require". # You do NOT need to know perl to edit the paths; it should be fairly -# self-explanatory +# self-explanatory and easy to maintain perl syntax :-) # -------------------------------------- @@ -88,6 +88,16 @@ $PERSONAL=""; # NOTE: whatever value you choose, for security reasons it is better to make # it fully qualified -- that is, starting with "refs/" +# -------------------------------------- + +# if git on your server is on a standard path (that is +# ssh git@server git --version +# works), leave this setting as is. Otherwise, choose one of the +# alternatives, or write your own + +$GIT_PATH=""; +# $GIT_PATH="/opt/bin/"; + # -------------------------------------- # per perl rules, this should be the last line in such a file: 1; diff --git a/doc/0-INSTALL.mkd b/doc/0-INSTALL.mkd index c54c7eb..5ef38cf 100644 --- a/doc/0-INSTALL.mkd +++ b/doc/0-INSTALL.mkd @@ -1,98 +1,75 @@ # installing gitolite -### pre-requisites +This document tells you how to install gitolite. After the install is done, +you may want to see the "admin" document for adding users, repos, etc. -If you managed to install git, you might already have what gitolite needs: +There's an easy install script for Linux, and for other Unixes there's a +slightly more manual process. Both are explained here. - * git itself, the more recent the better - * perl, typically installed with git, since git sort of needs it; any - version that includes `Data::Dumper`[1] will do. - * one user account on the server, with password access [2] +In this document: -A major objective is to allow use by people without root access, permissions -to create other userids, etc. Even if you have root, please add a user just -for gitolite and do all this from that user. - -### getting a tar file from a clone - -You can clone the repo from github, then execute a make command to extract a -tar file of the branch you want. Please use the make command, not a plain -"git archive". The comments in the `Makefile` will explain why. - - git clone git://github.com/sitaramc/gitolite.git - cd gitolite - make master.tar - # or maybe "make rebel.tar" or "make pu.tar" - -### quick install from tar file - - * make a temp directory somewhere, cd to it, and unpack the tar file - * run `src/install.pl` and follow the prompts - -**When you are told to edit some file, please read the comments in the file**. -And if you can make some time to read the documentation, please do. -Especially if you have problems. - -Notes: - - * At present the location of `~/.gitolite.rc` is fixed (maybe later I'll - change it to a "git config" variable but I don't see much need right now) - - If you edit it and change any paths, be sure to keep the perl syntax -- - you *don't* have to know perl to do so, it's fairly easy to guess in this - limited case. And of course, make sure you adjust the commands shown - above to suit the new locations - - * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`, - though you can change its location in the "rc" file. Edit the file as you - wish. The comments in the file ought to be clear enough but let me know - if not - - * if you want to bring in existing (bare, server) repos into gitolite, this - should work (refer to `~/.gitolite.rc` for *your* values of the pathnames - below): - * backup the repo, then move it to `$BASE_REPO` - * copy `$GL_ADMINDIR/src/update-hook.pl` to - `[reponame].git/hooks/update` -- if you don't do this, per branch - restrictions will not work - * then update the keys and the config file and "compile" (see "admin" - document) - -### Footnotes: - -[1] Actually, due to the way gitolite is architected, you can manage -without `Data::Dumper` on the server if you have no choice. Only -`gl-compile-conf` needs it, so just run that on some other machine and copy -the two output files across. Cumbersome but doable... the advantage of -separating all the hard work into a manually-run piece :) - -[2] If you have *only* pubkey access, and **no** password access, then your -pubkey is already in the server's `~/.ssh/authorized_keys`. If you also need -to access git as a developer (clone, push, etc), do *not* submit this same -pubkey to gitolite -- it won't work. - -Instead, create a different keypair for your "developer" role (by, e.g., -`ssh-keygen -t rsa -f ~/.ssh/gitdev`), then give `~/.ssh/gitdev.pub` to -gitolite as "yourname.pub", just like you would do for any other user. - -Then you create a suitable `~/.ssh/config` to use the correct key -automatically, something like this: - - host gitadm - hostname my.server - user my_userid_on_server - - host gitdev - hostname my.server - user my_userid_on_server - identityfile ~/.ssh/gitdev - -From now on, `ssh gitadm` will get you a command line on the server, to do -gitolite admin and other work. And your repository URLs would look like -`gitdev:reponame.git`. Very, very, simple... - -And as with gitosis, there's more "ssh" magic than "git" magic here :-) + * easy install + * manual install + * other notes + * next steps ---- -gitolite is released under the GPL v2 license. See COPYING for details +### easy install + +There is an easy install script that makes installing very easy for the common +case. **This script is meant to be run on your workstation, not on the +server!** It will take care of all the server side work, *and* get you +"push-to-admin" too :-) In short, it does **everything**! + +Assumptions: + + * you have a server to host gitolite + * git is installed on that server (and so is perl) + * you have a userid on that server + * you have ssh-pubkey (password-less) login to that userid + * (if you have only password access, run `ssh-keygen -t rsa` to create a + new keypair if needed, then run `ssh-copy-id user@host`) + * you have a clone or an archive of gitolite somewhere on your workstation + +If so, just `cd` to that clone and run `src/00-easy-install.sh` and follow the +prompts! (Running it without any arguments shows you usage plus other useful +info). + +#### advantages over the older install methods + + * all ssh problems reduced to **just one pre-requisite**: enable ssh pubkey + (password-less) access to the server from your workstation first + * the script takes care of all the server side work + * when done: + * you get two different pubkeys (the original one for command line + access as before, plus a new one, created by the script, for gitolite + access) + * you can admin gitolite by commit+push a "gitolite-admin" repo, just + like gitosis (i.e., full "push to admin" power!) + +#### disadvantages + + * has been tested only with Linux + +### manual install + +If for some reason you cannot use the easy-install method, (for example, +you're on a non-Linux machine), it's not very complicated. Just open the file +`src/00-easy-install.sh` in a nice, syntax coloring, text editor, and follow +the instructions marked "MANUAL" :-) + +### other notes + + * If you edit `~/.gitolite.rc` and change any paths, be sure to keep the + perl syntax -- you *don't* have to know perl to do so, it's fairly easy to + guess in this limited case + + * the config file is (by default) at `~/.gitolite/conf/gitolite.conf`, + though you can change its location in the "rc" file. Edit the file as you + wish. The comments in the example file (`conf/example.conf`) ought to be + clear enough but let me know if not + +### next steps + +See the "admin" document for how to add users, etc. diff --git a/doc/0-UPGRADE.mkd b/doc/0-UPGRADE.mkd index c106bc7..5e242f6 100644 --- a/doc/0-UPGRADE.mkd +++ b/doc/0-UPGRADE.mkd @@ -1,11 +1,19 @@ # upgrading gitolite atomically +Upgrading is done **manually, on the server** (except the last step, which is +on your admin repo clone), even if you installed it using the easy install +script on the client. First, it's not as difficult as an install so you don't +really need a script. Second, you may have customised the "rc" file +(`~/.gitolite.rc` on the server) and I'm reluctant to mess with that in an +automated way. + ### general upgrade notes If you follow the steps below, you can make the upgrade "atomic", so you don't have to do it at a "quiet" time or something. -1. untar the new version to some temp directory and `cd` to it +1. copy a tar file containing the new version to the server, untar it to some + temp directory and `cd` to it 2. *prepare* the new version of `~/.gitolite.rc`. It **must** have **all** the variables defined in `conf/example.gitolite.rc` (the "new" rc file), @@ -31,12 +39,11 @@ have to do it at a "quiet" time or something. src/install.pl 5. compile the config once again, in case the *internal* format of the - compiled config file (`$GL_CONF_COMPILED`) has changed + compiled config file (`$GL_CONF_COMPILED`) has changed. - src/gl-compile-conf - - (if you've already setup "push-to-admin", this step should be replaced by - a "git push". Make a dummy commit if needed, to make the push happen). + To do this, you have to do a "git push" on the client side. That might + require a dummy change (maybe add a blank line somewhere) because + otherwise the push will not happen. And you're done. @@ -45,6 +52,27 @@ And you're done. If any extra steps beyond the generic ones above are needed, they will be listed here, newest first. +#### upgrading from 410c9ba + +Between 410c9ba and this version, gitolite managed to make "push to admin" the +default for new installs, but in a much more painless way. If you're +upgrading, you're not forced to use "push to admin", but I'd suggest you: + + * make sure you have password-less (pubkey) login to a command line on your + server + * save your `~/.gitolite.rc`, `keydir/*.pub` and your `conf/gitolite.conf` + files from the server, bring them to your workstation + * then run `src/00-easy-install.sh` on the workstation, as if it were a + fresh install + * when the editor pops up to edit the rc file, delete all the lines in + it and copy them from the saved `~/.gitolite.rc` + * at the end of the script, after the gitolite-admin repo has been + cloned successfully, copy the saved `conf/gitolite.conf` and + `keydir/*.pub` to the clone, then add, commit, and push + +Gitolite also learnt to delegate parts of the config to other users. See +`doc/5-delegation.mkd` for details. + #### upgrading from 8217ef9 Between 8217ef9 and this version, gitolite learnt to handle gitweb/daemon diff --git a/doc/1-migrate.mkd b/doc/1-migrate.mkd index fe0b4cf..fb0491f 100644 --- a/doc/1-migrate.mkd +++ b/doc/1-migrate.mkd @@ -3,14 +3,9 @@ [TODO: make the migration tool fix up gitweb and daemon control also...] Migrating from gitosis to gitolite is pretty easy, because the basic design is -the same. The differences are: +the same. - * gitolite does not use a special repo for the configuration, pubkeys, etc. - You can choose to version that directory but it is not required that you - do so - -Here's how we migrated my work repos (note: substitute real paths, from your -`~/.gitolite.rc`, for `$REPO_BASE` and `$GL_ADMINDIR` below): +Here's how we migrated my work repos: 1. login as the `git` user on the server, and get a bash shell prompt @@ -18,10 +13,17 @@ Here's how we migrated my work repos (note: substitute real paths, from your else. This will prevent users from pushing anything while you do the backup, migration, etc. -3. For added safety, **delete** the post-update hook that gitosis-admin +3. **edit** `~/.ssh/authorized_keys` and **carefully** remove all the lines + containing "gitosis-serve", as well as the marker line that says + "auto-generated by gitosis, DO NOT REMOVE", then save the file. If the + file did not have any other keys and is now empty, don't worry -- save it + anyway because gitolite expects the file to be present (even if it is + empty). + +4. For added safety, **delete** the post-update hook that gitosis-admin installed - rm $REPO_BASE/gitosis-admin.git/hooks/post-update + rm ~/repositories/gitosis-admin.git/hooks/post-update or at least rename it to `.sample` like all the other hooks hanging around, or edit it and comment out the line that calls `gitosis-run-hook @@ -30,39 +32,34 @@ Here's how we migrated my work repos (note: substitute real paths, from your If you do not do this, an accidental push to the gitosis-admin repo will mess up your `~/.ssh/authorized_keys` file -4. take a **backup** of the `$REPO_BASE` directory +5. take a **backup** of the `~/repositories` directory -5. untar gitolite to some temporary directory and follow the instructions to - **install** it using `src/install.pl` +Now, log off the server and get back to the client: -6. **convert** your gitosis config file: +1. follow instructions to install gitolite; see install document. Make sure + that you **don't** change the default path for `$REPO_BASE`! - cd $GL_ADMINDIR - src/conf-convert.pl < ~/.gitosis.conf > conf/gitolite.conf +2. **convert** your gitosis config file. Substitute the path for your + gitosis-admin clone in `$GSAC` below, and similarly the path for your + gito**lite**-admin clone in `$GLAC` - be sure to check the file to make sure it converted correctly + src/conf-convert.pl < $GSAC/gitosis.conf > $GLAC/gitolite.conf -7. **copy** the update hook to each of the existing repos (if you have repos - in subdirectories, this won't work as is; adapt it): + Be sure to check the file to make sure it converted correctly - for i in $REPO_BASE/*.git - do - cp src/update-hook.pl $i/hooks/update - done +3. **copy** the keys from gitosis's keydir (same meanings for GSAC and GLAC) -8. **copy** the keys from gitosis's keydir + cp $GSAC/keydir/* $GLAC/keydir - cp $REPO_BASE/gitosis-admin.git/gitosis-export/keydir/* keydir - -9. **Important: expand** any multi-key files you may have. See the "faq, +4. **Important: expand any multi-key files you may have**. See the "faq, tips, etc" document in the doc directory for an explanation of what multi-keys are, how gitosis does them and how gitolite does it differently. You can split the keys manually, or use the following code (just - copy-paste it into your xterm): + copy-paste it into your xterm after "cd"-ing to your gitolite-admin repo + clone): - cd $GL_ADMINDIR wc -l keydir/*.pub | grep -v total | grep -v -w 1 | while read a b do i=1 @@ -82,12 +79,4 @@ Here's how we migrated my work repos (note: substitute real paths, from your "sitaram@laptop.pub" and "sitaram@desktop.pub" or whatever. *Please check the files to make sure this worked properly* -10. **edit** `~/.ssh/authorized_keys` and **carefully** remove all the lines - containing "gitosis-serve", as well as the marker line that says - "auto-generated by gitosis, DO NOT REMOVE", then save the file. If the - file did not have any other keys and is now empty, don't worry -- save it - anyway because gitolite expects the file to be present (even if it is - empty). - -At this point you're ready to "compile" the configuration. See the "admin" -document for what to do, and how to check the outputs, etc. +5. Check all your changes to your gitolite-admin clone, commit, and push diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 8cd2216..133455a 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -22,15 +22,15 @@ Please read on to see how to do this correctly. extension, like `sitaram.pub` or `john-smith.pub`. You can also use periods and underscores - * copy all these `*.pub` files to `$GL_KEYDIR` + * copy all these `*.pub` files to `keydir` in your gitolite-admin repo clone - * the config file (`$GL_CONF`) is very well commented, please take a couple - of minutes to read it. Then edit it and + * edit the config file (`conf/gitolite.conf` in your admin repo clone). See + `conf/example.conf` in the gitolite source for details on what goes in + that file, syntax, etc. Just add new repos as needed, and add new users + and give them permissions as required. The users names should be exactly + the same as their keyfile names, but without the `.pub` extension - * add new repos as needed - * add new users and give them permissions as required. The users names - should be exactly the same as their keyfile names, but without the - `.pub` extension + * when done, commit your changes and push #### specifying gitweb and daemon access @@ -51,41 +51,6 @@ one-time setup you must do separately. All this does is: value you specified for `$projects_list` when setting up gitweb) * for daemon, create the file `git-daemon-export-ok` in the repository -`src/gl-compile-conf` will keep these files consistent with the config -settings -- this includes removing such settings if you remove "read" -permissions for the special usernames. - -#### compiling - - * backup your `~/.ssh/authorized_keys` file if you feel nervous :-) - * that's "backup" as in "copy", not "move". The next step won't work if - the file doesn't exist. Even an empty one is fine but it must be - present - * if you don't have an `~/.ssh/authorized_keys` file at all, you may - have logged in with a password, which in turn might mean you are not - familiar with ssh and authkeys etc. If so, please read up at least - [this](http://sitaramc.github.com/0-installing/9-gitolite-basics.html#IMPORTANT_overview_of_ssh), - and preferably also the man pages for sshd and sshd\_config, to make - sure you understand the security implications of what you are doing. - Once you have understood that, create at least an empty - `~/.ssh/authorized_keys` file before proceeding to the next step - - * cd to `$GL_ADMINDIR` and run `src/gl-compile-conf` - -That should be it, really. However, if you want to be doubly sure, or maybe -the first couple of times you use it, you may want to check these: - - * check the outputs - - * `~/.ssh/authorized_keys` should contain one line for each "user" pub - key added, between two "marker" lines (which you should please please - not remove!). The line should contain a "command=" pointing to a - `$GL_ADMINDIR/src/gl-auth-command` file, then some sshd restrictions, the - key, etc. - * `$GL_CONF_COMPILED` should contain an expanded list of the access - control rules. It may look a little long, but it's fairly intuitive! - - * if the run threw up any "initialising empty repo" messages, check the - individual repos (inside `$REPO_BASE`) if you wish. Especially make sure - the `$REPO_BASE/[reponame].git/hooks/update` got copied OK and is - executable +The "compile" script will keep these files consistent with the config settings +-- this includes removing such settings if you remove "read" permissions for +the special usernames. diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 66ab956..cda6ab0 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -5,19 +5,22 @@ In this document: * common errors and mistakes * git version dependency * other errors, warnings, notes... + * getting a tar file from a clone * differences from gitosis * simpler syntax * two levels of access rights checking * error checking the config file * delegating parts of the config file * easier to specify gitweb/daemon access - * built-in logging + * better logging * one user, many keys + * support for git installed outside default PATH * who am I? * other cool things - * developer specific branches + * "personal" branches * design choices * why we don't do "excludes" + * keeping the parser and the access control separate ### common errors and mistakes @@ -37,10 +40,10 @@ In this document: Here's a workaround for a version dependency that the normal flow of gitolite has. -When you edit your config file to create a new repo, and run -`src/gl-compile-conf`, gitolite creates an empty, bare repo for you. -Normally, you're expected to clone this on the client side, and start working --- make your first commit(s), then push, etc. +When you edit your config file to create a new repo, and push the changes to +the server, gitolite creates an empty, bare repo for you. Normally, you're +expected to clone this on the client side, and start working -- make your +first commit(s), then push, etc. However, cloning an empty repo requires a server side git version that is at least 1.6.2. Gitolite detects this when creating a repo, and warns you. @@ -74,21 +77,27 @@ normal way, since it's not empty anymore. * if you specify a repo that is not at the top level `$REPO_BASE`, be sure to manually create the intermediate directories first. For instance if - you specify a new repo called "a/b/c" to the config file and "compile", - the "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has - already been created - - * if you run `git init` inside `$GL_ADMINDIR` (that is, make it a normal, - non-bare, repo), then, everytime you "compile" (run - `src/gl-compile-conf`), any changes to `conf` and `keydir` will - automatically be committed. This is a simple safety net in case you - accidentally delete the whole config or something. Also see - [4-push-to-admin.mkd](http://github.com/sitaramc/gitolite/blob/pu/doc/4-push-to-admin.mkd) - if you really know what you're doing and want "push to admin" + you specify a new repo called "a/b/c" to the config file and push, the + "compile" script will just `mkdir a/b/c.git`, assuming "a/b" has already + been created * gitweb not able to read your repos? You can change the umask for newly created repos to something more relaxed -- see the `~/.gitolite.rc` file +### getting a tar file from a clone + +You can clone the repo from github or indefero, then execute a make command to +extract a tar file of the branch you want. Please use the make command, not a +plain "git archive", because the Makefile adds a file called +`.GITOLITE-VERSION` that will help you identify which version you are using. + + git clone git://github.com/sitaramc/gitolite.git + # (OR) + git clone git://sitaramc.indefero.net/sitaramc/gitolite.git + cd gitolite + make master.tar + # or maybe "make rebel.tar" or "make pu.tar" + ### differences from gitosis Apart from the big ones listed in the top level README, and subjective ones @@ -176,9 +185,8 @@ gitosis does not do any. I just found out that if you mis-spell `members` as `member`, gitosis will silently ignore it, and leave you wondering why access was denied. -In gitolite, you have to "compile" the config file first (this step takes the -place of the commit+push in gitosis), and keyword typos *are* caught so you -know right away. +Gitolite "compiles" the config file first and keyword typos *are* caught so +you know right away. #### delegating parts of the config file @@ -219,24 +227,21 @@ bits and pieces. Here's an example, using short repo names for convenience: repo r2 # ...and so on... -#### built-in logging +#### better logging -...just in case of emergency :-) +If you have been too liberal with the permission to rewind, it has built-in +logging as an emergency fallback if someone goes too far, or for audit +purposes [`*`]. The logfile names and location are configurable, and can +include the year/month/day etc in the filename for easy archival or further +processing. The log file even tells you which pattern in the config file +matched to allow that specific access to proceed. -Let's say you gave a dev the right to rewind a branch and he went and rewound -it all the way, or pushed something drastically different on it. Now you need -to recover the commit that got wiped out. +> [`*`] setting `core.logAllRefUpdates true` does provide a safety net +> against over-zealous rewinds, but it does not tell you "who". And +> strangely, management does not seem to share the view that "blame" is just +> a synonym for "annotate" ;-)] -If you'd remembered to `git config core.logAllRefUpdates` for that repo, or -globally, you'd be fine -- the reflog will tell you. Otherwise you'd be left -grubbing around in `git fsck --unreachable` a bit :-( - -And even if you recover the correct commit, you'll never know *who* did it -- -not unless you add a one-line patch to gitosis, plus a `post-receive` hook to -every repository. - -With gitolite, there's a log file in `$GL_ADMINDIR` that contains lines like -this: +The log lines look like this: 2009-09-19.10:24:37 + b4e76569659939 4fb16f2a88d8b5 myrepo refs/heads/master user2 refs/heads/master @@ -259,9 +264,35 @@ each of my pubkeys. In gitolite, we keep them separate: "sitaram@laptop.pub" and "sitaram@desktop.pub". The part before the "@" is the username, so gitolite knows these two keys belong to the same person. +Note that you don't say "sitaram@laptop" and so on in the **config** file -- +as far as the config file is concerned there's just **one** user called +"sitaram" -- so you only say "sitaram" there. Only the **pubkey files** have +the extra "@" stuff. + I think this is easier to maintain if you have to delete or change one of those keys. +#### support for git installed outside default PATH + +The normal solution is to add to the system default PATH somehow, either by +munging `/etc/profile` or by enabling `PermitUserEnvironment` in +`/etc/ssh/sshd_config` and then setting the PATH in `~/.ssh/.environment`. +All these are security risks because they allow a lot more than just you and +your git install :-) + +And if you don't have root, you can't do this anyway. + +The only solution till now has been to ask every client to set the config +parameters `remote..receivepack` and `remote..uploadpack`. But +telling *every* client to do so is a pain... + +Gitolite lets you specify the directory in which git binaries are to be found, +via a new variable (`$GIT_PATH`) in the "rc" file. If this variable is +non-empty, it will be appended to the PATH environment variable before +attempting to run git stuff. + +Very easy, very simple, and completely transparent to the users :-) + #### who am I? As a developer, I send a file called `id_rsa.pub` to the gitolite admin. He @@ -283,33 +314,31 @@ In gitolite, it's simple: just ask nicely :-) ### other cool things -#### developer specific branches +#### "personal" branches -So I know what gitolite calls me. Big deal... who cares? +"personal" branches are great for corporate environments, where +unauthenticated pull/clone is a no-no. Since a dev workstation cannot do +authentication, even work shared just between 2 devs has to go *via* the +server. This causes the same branch name clutter as in a centralised VCS, +plus setting up permissions for this becomes a chore for the admin. -Here is an idea: give every developer a personal "scratch" namespace within -which she can create, rewind, or delete any branch. For example, I would own -anything under +gitolite lets you define a "personal" or "scratch" namespace prefix for +each developer (e.g., `refs/personal//*`), with full +permissions for that dev and read-only for everyone else. And you get +this without adding a single line to the access config file -- pretty +much fire and forget as far as the admin is concerned, even if there is +constant churn in the project teams. - $PERSONAL_BRANCH_PREFIX/sitaram/ +Not bad for something that took just *one* line of code to implement. +And that's one clean, readable, line, by the way ;-) -The admin could set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate +The admin would set `$PERSONAL_BRANCH_PREFIX` in the rc file and communicate this to all users. It could be something like `refs/heads/personal`, which means all such branches will show up in `git branch` lookups and `git clone` will fetch them. Or he could use, say, `refs/personal`, which means it won't show up in any normal "branch-y" commands and stuff, and generally be much less noisy. -Yes, I know git is all about allowing private branches, but in a corporate -environment it's not always possible to pull from a co-worker, for the same -reasons you don't have anonymous access (like the git:// protocol). A normal -developer workstation cannot do authentication, so how would they know who's -pulling? This is a perfect way to share code *without* cluttering the global -namespace, and each developer controls his/her own set of branches! - -The amount of code needed? *One line!* I'll spend about 3x more on declaring -and initialising the new variable, and 30x more on documenting it :-) - **Note that a user who has NO write access cannot have personal branches**; if you read the section (above) on "two levels of access rights checking" you'll understand why. @@ -341,8 +370,31 @@ Just don't *show* the user this config file; it might sound insulting :-) ### design choices +#### keeping the parser and the access control separate + +There are two programs concerned with access control: + + * `gl-auth-command`, the program that is run via `~/.ssh/authorized_keys`; + this decides whether git should even be allowed to run (basic R/W/no + access). (This one cannot decide on the branch-level access; it is not + known at this point what branch is being accessed) + * the update-hook on each repo, which decides the per-branch permissions + +I have chosen to keep the relatively complex task of parsing the config file +out of them to keep them simpler (and faster). So any changes to the config +have to be first "compiled", and the access control programs use this +"compiled" version of the config. (The compile step also refreshes +`~/.ssh/authorized_keys`). + +If you choose the "easy install" method, all this is quite transparent to you +anyway. If you cannot use the easy install and must install manually, I have +clear instructions on how to set it up. + #### why we don't do "excludes" +[umm... having said all this, I implemented it anyway; see the "rebel" +branch!] + I found an error in the example conf file. This snippet *seems* to say that "bruce" can write versioned tags (`refs/tags/v[0-9].*`), but the other staffers can't: @@ -387,3 +439,5 @@ The lack of overlap between refexes ensures ***no confusion*** in specifying, understanding, and ***auditing***, what is allowed and what is not. And in security, "no confusion" is a good thing :-) + + diff --git a/doc/4-push-to-admin.mkd b/doc/4-push-to-admin.mkd index 48d1baa..8791480 100644 --- a/doc/4-push-to-admin.mkd +++ b/doc/4-push-to-admin.mkd @@ -1,5 +1,10 @@ # "push to admin" in gitolite +**WARNING: THIS DOCUMENT IS OBSOLETE. DO NOT USE. IT IS RETAINED ONLY FOR +HISTORICAL PURPOSES**. Gitolite now does "push-to-admin" by default, and does +it very easily and simply by front-loading the ssh problem. See the install +doc for details. + ---- Gitosis's default mode of admin is by cloning and pushing the `gitosis-admin` diff --git a/src/00-easy-install.sh b/src/00-easy-install.sh new file mode 100755 index 0000000..a9d8700 --- /dev/null +++ b/src/00-easy-install.sh @@ -0,0 +1,362 @@ +#!/bin/bash + +# easy install for gitolite + +# you run this on the client side, and it takes care of all the server side +# work. You don't have to do anything on the server side directly + +# to do a manual install (since I have tested this only on Linux), open this +# script in a nice, syntax coloring, text editor and follow the instructions +# prefixed by the word "MANUAL" in the comments below :-) + +# run without any arguments for "usage" info + +# important setting: bail on any errors (else we have to check every single +# command!) +set -e + +die() { echo "$@"; echo; echo "run $0 again without any arguments for help and tips"; exit 1; } +prompt() { + echo + echo + echo ------------------------------------------------------------------------ + echo " $1" + echo + read -p '...press enter to continue or Ctrl-C to bail out' +} +usage() { + cat </dev/null || + die "cant find at least some files in gitolite sources/config; aborting" + +# MANUAL: make sure you have password-less (pubkey) auth on the server. That +# is, running "ssh git@server" should log in straight away, without asking for +# a password + +ssh -p $port -o PasswordAuthentication=no $user@$host true || + die "pubkey access didn't work; please set it up using 'ssh-copy-id' or something" + +# MANUAL: make sure there's no "gitolite-admin" directory in $HOME (actually +# for the manual flow this doesn't matter so much!) + +[[ -d $HOME/gitolite-admin ]] && + die "please delete or move aside the \$HOME/gitolite-admin directory" + +# MANUAL: create a new key for you as a "gitolite user" (as opposed to you as +# the "gitolite admin" who needs to login to the server and get a command +# line). For example, "ssh-keygen -t rsa ~/.ssh/sitaram"; this would create +# two files in ~/.ssh (sitaram and sitaram.pub) + +prompt "the next command will create a new keypair for your gitolite access + + The pubkey will be $HOME/.ssh/$admin_name.pub. You will have to + choose a passphrase or hit enter for none. I recommend not having a + passphrase for now, and adding one with 'ssh-keygen -p' *as soon as* + all the setup is done and you've successfully cloned and pushed the + gitolite-admin repo. + + After that, I suggest you (1) install 'keychain' or something + similar, and (2) add the following command to your bashrc (since + this is a non-default key) + + ssh-add \$HOME/.ssh/$admin_name + + This makes using passphrases very convenient." + +if [[ -f $HOME/.ssh/$admin_name.pub ]] +then + prompt "Hmmm... pubkey $HOME/.ssh/$admin_name.pub exists; should I just re-use it? + Be sure you remember the passphrase, if you gave one when you created it!" +else + ssh-keygen -t rsa -f $HOME/.ssh/$admin_name || die "ssh-keygen failed for some reason..." +fi + +# MANUAL: copy the pubkey created to the server, say to /tmp. This would be +# "scp ~/.ssh/sitaram.pub git@server:/tmp" (the script does this at a later +# stage, you do it now for convenience). Note: only the pubkey (sitaram.pub). +# Do NOT copy the ~/.ssh/sitaram file -- that is a private key! + +# MANUAL: if you're running ssh-agent (see if you have an environment variable +# called SSH_AGENT_PID in your "env"), you should add this new key. The +# command is "ssh-add ~/.ssh/sitaram" + +if ssh-add -l &>/dev/null +then + prompt "you're running ssh-agent. We'll try and do an ssh-add of the + private key we just created, otherwise this key won't get picked up. If + you specified a passphrase in the previous step, you'll get asked for one + now -- type in the same one." + + ssh-add $HOME/.ssh/$admin_name +fi + +# MANUAL: you now need to add some lines to the end of your ~/.ssh/config +# file. If the file doesn't exist, create it. Make sure the file is "chmod +# 644". + +# The lines to be included look like this: + +# host gitolite +# user git +# hostname server +# port 22 +# identityfile ~/.ssh/sitaram + +echo " +host gitolite + user $user + hostname $host + port $port + identityfile ~/.ssh/$admin_name" > $HOME/.ssh/.gl-stanza + +if grep 'host *gitolite' $HOME/.ssh/config &>/dev/null +then + prompt "your \$HOME/.ssh/config already has settings for gitolite. I will + assume they're correct, but if they're not, please edit that file, delete + that paragraph (that line and the following few lines), Ctrl-C, and rerun. + + In case you want to check right now (from another terminal) if they're + correct, here's what they are *supposed* to look like: +$(cat ~/.ssh/.gl-stanza)" + +else + prompt "creating settings for your gitolite access in $HOME/.ssh/config; + these are the lines that will be appended to your ~/.ssh/config: +$(cat ~/.ssh/.gl-stanza)" + + cat $HOME/.ssh/.gl-stanza >> $HOME/.ssh/config + # if the file didn't exist at all, it might have the wrong permissions + chmod 644 $HOME/.ssh/config +fi +rm $HOME/.ssh/.gl-stanza + +# ---------------------------------------------------------------------- +# client side stuff almost done; server side now +# ---------------------------------------------------------------------- + +# MANUAL: copy the gitolite directories "src", "conf", and "doc" to the +# server, to a directory called (for example) "gitolite-install". You may +# have to create the directory first. + +ssh -p $port $user@$host mkdir -p gitolite-install +rsync -e "ssh -p $port" -a src conf doc $user@$host:gitolite-install/ + +# MANUAL: now log on to the server (ssh git@server) and get a command line. +# This step is for your convenience; the script does it all from the client +# side but that may be too much typing for manual use ;-) + +# MANUAL: cd to the "gitolite-install" directory where the sources are. Then +# copy conf/example.gitolite.rc as ~/.gitolite.rc and edit it if you wish to +# change any paths. Make a note of the GL_ADMINDIR and REPO_BASE paths; you +# will need them later + +prompt "the gitolite rc file needs to be edited by hand. The defaults + are sensible, so if you wish, you can just exit the editor. + + Otherwise, make any changes you wish and save it. Read the comments to + understand what is what -- the rc file's documentation is inline. + + Please remember this file will actually be copied to the server, and that + all the paths etc. represent paths on the server!" + +# lets try and get the file from there first +if scp -P $port $user@$host:.gitolite.rc . +then + prompt "Oh hey... you already had a '.gitolite.rc' file on the server. + Let's see if we can use that instead of the default one..." + sort < .gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.old + sort < conf/example.gitolite.rc | perl -ne 'print "$1\n" if /^(\$\w+) *=/' > glrc.new + if diff -u glrc.old glrc.new + then + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc + else + prompt " looks like you're upgrading! I'm going to run your editor + with *both* the old and the new files (in that order), so you can add + in the lines pertaining to the variables shown with a '+' sign in the + above diff. This is necessary; please dont skip this + + [It's upto you to figure out how your editor handles 2 filename + arguments, switch between them, copy lines, etc ;-)]" + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc conf/example.gitolite.rc + fi +else + cp conf/example.gitolite.rc .gitolite.rc + ${VISUAL:-${EDITOR:-vi}} .gitolite.rc +fi + +# copy the rc across +scp -P $port .gitolite.rc $user@$host: + +prompt "ignore any 'please edit this file' or 'run this command' type + lines in the next set of command outputs coming up. They're only relevant + for a manual install, not this one..." + +# extract the GL_ADMINDIR and REPO_BASE locations +GL_ADMINDIR=$(ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$GL_ADMINDIR'") +REPO_BASE=$( ssh -p $port $user@$host "perl -e 'do \".gitolite.rc\"; print \$REPO_BASE'") + +# MANUAL: still in the "gitolite-install" directory? Good. Run +# "src/install.pl" + +ssh -p $port $user@$host "cd gitolite-install; src/install.pl" + +# MANUAL: setup the initial config file. Edit $GL_ADMINDIR/conf/gitolite.conf +# and add at least the following lines to it: + +# repo gitolite-admin +# RW+ = sitaram + +echo "#gitolite conf +#please see conf/example.conf for details on syntax and features + +repo gitolite-admin + RW+ = $admin_name + +repo testing + RW+ = @all + +" > gitolite.conf + +# send the config and the key to the remote +scp -P $port gitolite.conf $user@$host:$GL_ADMINDIR/conf/ + +scp -P $port $HOME/.ssh/$admin_name.pub $user@$host:$GL_ADMINDIR/keydir + +# MANUAL: cd to $GL_ADMINDIR and run "src/gl-compile-conf" + +ssh -p $port $user@$host "cd $GL_ADMINDIR; src/gl-compile-conf" + +# ---------------------------------------------------------------------- +# hey lets go the whole hog on this; setup push-to-admin! +# ---------------------------------------------------------------------- + +# MANUAL: make the first commit in the admin repo. This is a little more +# complex, so read carefully and substitute the correct paths. What you have +# to do is: + +# cd $REPO_BASE/gitolite-admin.git +# GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir +# GIT_WORK_TREE=$GL_ADMINDIR git commit -am start + +# Substitute $GL_ADMINDIR and $REPO_BASE appropriately. Note there is no +# space around the "=" in the second and third lines. + +echo "cd $REPO_BASE/gitolite-admin.git +GIT_WORK_TREE=$GL_ADMINDIR git add conf/gitolite.conf keydir +GIT_WORK_TREE=$GL_ADMINDIR git commit -am start --allow-empty +" | ssh -p $port $user@$host + +# MANUAL: now that the admin repo is created, you have to set the hooks +# properly. The install program does this. So cd back to the +# "gitolite-install" directory and run "src/install.pl" + +ssh -p $port $user@$host "cd gitolite-install; src/install.pl" + +prompt "now we will clone the gitolite-admin repo to your workstation + and see if it all hangs together. We'll do this in your \$HOME for now, + and you can move it elsewhere later if you wish to." + +# MANUAL: you're done! Log out of the server, come back to your workstation, +# and clone the admin repo using "git clone gitolite:gitolite-admin.git"! + +cd $HOME +git clone gitolite:gitolite-admin.git + +# MANUAL: be sure to read the message below; this applies to you too... + +echo +echo +echo ------------------------------------------------------------------------ +echo "Cool -- we're done. Now you can edit the config file (currently +in ~/gitolite-admin/conf/gitolite.conf) to add more repos, users, etc. +When done, 'git add' the changed files, 'git commit' and 'git push'. + +Read the comments in conf/example.conf for information about the config +file format -- like the rc file, this also has inline documentation. + +Your URL for cloning any repo on this server will be + + gitolite:reponame.git + +However, any other users you set up will have to use + + $user@$host:reponame.git + +unless they also create similar settings in their '.ssh/config' file." diff --git a/src/gl-auth-command b/src/gl-auth-command index 62573d4..8201403 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -24,13 +24,16 @@ use warnings; # ---------------------------------------------------------------------------- -our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE); +our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH); our %repos; my $glrc = $ENV{HOME} . "/.gitolite.rc"; die "parse $glrc failed: " . ($! or $@) unless do $glrc; die "parse $GL_CONF_COMPILED failed: " . ($! or $@) unless do $GL_CONF_COMPILED; +# add a custom path for git binaries, if specified +$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; + # ---------------------------------------------------------------------------- # definitions specific to this program # ---------------------------------------------------------------------------- diff --git a/src/gl-compile-conf b/src/gl-compile-conf index ad50c4a..f51f97a 100755 --- a/src/gl-compile-conf +++ b/src/gl-compile-conf @@ -47,7 +47,7 @@ $Data::Dumper::Indent = 1; # common definitions # ---------------------------------------------------------------------------- -our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST); +our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH); # now that this thing *may* be run via "push to admin", any errors have to # grab the admin's ATTENTION so he won't miss them among the other messages a @@ -57,6 +57,9 @@ my $ATTN = "\n\t\t***** ERROR *****\n "; my $glrc = $ENV{HOME} . "/.gitolite.rc"; die "$ATTN parse $glrc failed: " . ($! or $@) unless do $glrc; +# add a custom path for git binaries, if specified +$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; + # ---------------------------------------------------------------------------- # definitions specific to this program # ---------------------------------------------------------------------------- diff --git a/src/install.pl b/src/install.pl index 017854f..f6e4142 100755 --- a/src/install.pl +++ b/src/install.pl @@ -3,7 +3,7 @@ use strict; use warnings; -our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF); +our ($REPO_BASE, $GL_ADMINDIR, $GL_CONF, $GIT_PATH); # wrapper around mkdir; it's not an error if the directory exists, but it is # an error if it doesn't exist and we can't create it @@ -33,6 +33,9 @@ unless (-f $glrc) { # ok now $glrc exists; read it to get the other paths die "parse $glrc failed: " . ($! or $@) unless do $glrc; +# add a custom path for git binaries, if specified +$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH; + # mkdir $REPO_BASE, $GL_ADMINDIR if they don't already exist my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" ); wrap_mkdir($repo_base_abs); @@ -66,7 +69,10 @@ for my $repo (`find . -type d -name "*.git"`) { } # oh and one of those repos is a bit more special and has an extra hook :) -system("cp $GL_ADMINDIR/src/pta-hook.sh gitolite-admin.git/hooks/post-update"); -system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", - "gitolite-admin.git/hooks/post-update"); -chmod 0755, "gitolite-admin.git/hooks/post-update"; +if ( -d "gitolite-admin.git/hooks" ) { + print STDERR "copying post-update hook to gitolite-admin repo...\n"; + system("cp -v $GL_ADMINDIR/src/pta-hook.sh gitolite-admin.git/hooks/post-update"); + system("perl", "-i", "-p", "-e", "s(export GL_ADMINDIR=.*)(export GL_ADMINDIR=$GL_ADMINDIR)", + "gitolite-admin.git/hooks/post-update"); + chmod 0755, "gitolite-admin.git/hooks/post-update"; +}