mikkoi has asked for the wisdom of the Perl Monks concerning the following question:

I have written a blog post about how to set up one or more Git repositories available via SSH in a *private* account, not a dedicated git account. Because I am a shared hosting site user, I like to talk about problems that arise in such environments, e.g. that you only have your own account, and definitely no root access. To solve the mentioned problem, I have written a wrapper script for git-shell. Of course using taint check, among others. Before I publish the article, I'd like to ask if any security experts here could take a quick look on my solution.

Thank you. I am posting the script here. If anyone has more spare time (!) I'd be even happier to have someone proof read the article.

This script is called from ~/.ssh/authorized_keys file.

#!/usr/bin/env perl use strict; use warnings; use feature q{say}; my $GIT_COMMANDS = q{git-receive-pack|git-upload-pack|git-upload-archi +ve}; my $OUR_COMMANDS = q{help|list}; my $GIT_SHELL = q{/usr/bin/git-shell}; local ($ENV{HOME}) = $ENV{HOME} =~ m/^(.*)$/msx; # Untaint path sub log_info { ## no critic (Subroutines::RequireArgUnpacking) open my $logfile, '>>', $ENV{HOME} . q{/bin/git-shell-wrapper.log} + || die "Could not open log file!\n"; say {$logfile} @_; ## no critic (InputOutput::RequireCheckedSyscal +ls) my $ok = close $logfile; return; } sub allowed_repositories { my $list_cmd = $ENV{HOME} . q{/git-shell-commands/list}; my @paths = `$list_cmd`; ## no critic (InputOutput::ProhibitBackti +ckOperators) return map { m/^([^\n]+)$/msx; } @paths; } sub path_is_allowed { my $path = shift; return (grep { /^$path$/msx } allowed_repositories() ) == 1; } my $user = $ENV{USER}; log_info( q{User '}, $user, q{' logged in.} ); log_info( q{SSH_ORIGINAL_COMMAND: '}, $ENV{SSH_ORIGINAL_COMMAND}//q{}, + q{'.} ); local ($ENV{PATH}) = $ENV{PATH} =~ m/^(.*)$/msx; # Untaint path my ($arg_cmd, $arg_par) = split q{ }, $ENV{SSH_ORIGINAL_COMMAND}//q{}; log_info( q{arg_cmd:}, $arg_cmd//q{} ); log_info( q{arg_par:}, $arg_par//q{} ); if( defined $arg_cmd ) { if( $arg_cmd =~ m/^(?:$GIT_COMMANDS)$/msx ) { my ($git_cmd) = $arg_cmd =~ m/^($GIT_COMMANDS)$/msx; # Untaint + command if( defined $arg_par ) { my ($git_cmd_arg) = $arg_par =~ m/^(.+)$/msx; # Untaint my ($repo_path_candidate) = $git_cmd_arg =~ m/^'?([^']+)'? +$/msx; # Can have ' around path. my ($repo_path) = grep { /^$repo_path_candidate$/msx } all +owed_repositories(); if( defined $repo_path ) { log_info( q{repo_path: '}, $repo_path, q{'.} ); exec $GIT_SHELL, q{-c}, "$git_cmd '$repo_path'"; # Yes +, git-shell wants the params as one! } else { die q{Repository '}, $repo_path_candidate, q{' not ava +ilable!}, qq{\n}; } } else { die q{Command '}, $arg_cmd, q{' missing parameter <repo_pa +th>!}, qq{\n}; } } elsif( $arg_cmd =~ m/^(?:$OUR_COMMANDS)$/msx ) { my ($our_cmd) = $arg_cmd =~ m/^($OUR_COMMANDS)$/msx; # Untaint + command exec $GIT_SHELL, q{-c}, $our_cmd; } else { die q{Command '}, $arg_cmd, q{' not allowed!}, qq{\n}; } } else { exec $GIT_SHELL; }

This script allows three different ways of using git-shell:

Replies are listed 'Best First'.
Re: Perl, Git and SSH! Security Concerns
by AnomalousMonk (Archbishop) on Nov 06, 2018 at 04:18 UTC
    open my $logfile, '>>', $ENV{HOME} . q{/bin/git-shell-wrapper.log} || die "Could not open log file!\n";

    The die expression in the quoted statement can never be executed due to considerations of precedence WRT the  || operator. Workable variations would be
        open(my $logfile, '>>', $ENV{HOME} . q{/bin/git-shell-wrapper.log}) || die "Could not open log file!\n";
    (added grouping parentheses) or
        open my $logfile, '>>', $ENV{HOME} . q{/bin/git-shell-wrapper.log} or die "Could not open log file!\n";
    (use super-low precedence  or operator vice  || operator (update: this is the current idiomatic usage)). See perlop.


    Give a man a fish:  <%-{-{-{-<

Re: Perl, Git and SSH! Security Concerns
by Your Mother (Archbishop) on Nov 05, 2018 at 22:55 UTC

    No commentary on the code but I wanted to remark that bitbucket has unlimited private repos and competes more and more favorably with github on other fronts too.