diff --git a/hooks/templates/inc_config.php.erb b/hooks/templates/inc_config.php.erb index a7198aab85e5097f4e739be7481890ba40e8ad27..f9c97088e80d4b03072f1af417d3a996c615e082 100644 --- a/hooks/templates/inc_config.php.erb +++ b/hooks/templates/inc_config.php.erb @@ -1,13 +1,33 @@ <?php +$approot=dirname(__DIR__); return array( // define a secret aka pi key - 'apikey'=>'@replace["apikey"]', + 'apikey'=>'<%= @replace["apikey"] %>', - // local directory of synched ci packages - 'packagedir'=>dirname(__DIR__).'/packages', + // define a secret aka api key + 'apikey'=>'our-package-server-secret', - // allow directory listing when accessing a path of a package + // packages to deliver where files from ci server are synched + 'packagedir'=>$approot.'/packages', + + // max age of request ... client and server need to be in sync + 'maxage'=>60, + + // force that a hash can be used only once + // a side effect is that fast repeat or simultanius requests + // will be denied. + 'onetimesecret'=>true, + + // filesize of lock file with stored hashed before starting garbage collection + // 10.000 byte are reached after 114 req + 'maxlockfilesize'=>50000, + + // tmp dir to store used hashes + 'tmpdir'=>$approot.'/tmp', + + // allow directory listing when accessing a path of a package + // true is required to fetch all packages 'showdircontent'=>true, ); diff --git a/public_html/inc_config.php.dist b/public_html/inc_config.php.dist index 080cff887e1ca78ff8e6bf56fad98ff1490f0077..58ac35d54112d2aab69d1f40e8c65c45235e06c5 100644 --- a/public_html/inc_config.php.dist +++ b/public_html/inc_config.php.dist @@ -1,13 +1,30 @@ <?php +$approot=dirname(__DIR__); return array( - // define a secret aka pi key + // define a secret aka api key 'apikey'=>'our-package-server-secret', + + // packages to deliver where files from ci server are synched + 'packagedir'=>$approot.'/packages', + + // max age of request ... client and server need to be in sync + 'maxage'=>60, + + // force that a hash can be used only once + // a side effect is that fast repeat or simultanius requests + // will be denied. + 'onetimesecret'=>true, + + // filesize of lock file with stored hashed before starting garbage collection + // 10.000 byte are reached after 114 req + 'maxlockfilesize'=>10000, - // local directory of synched ci packages - 'packagedir'=>dirname(__DIR__).'/packages', + // tmp dir to store used hashes + 'tmpdir'=>$approot.'/tmp', // allow directory listing when accessing a path of a package + // true is required to fetch all packages 'showdircontent'=>true, ); \ No newline at end of file diff --git a/public_html/inc_functions.php b/public_html/inc_functions.php index 44a270457ff379e0ed371ddc177868669fbd492e..1756e2e095a5c88f8ace7dc89896173f9892358b 100644 --- a/public_html/inc_functions.php +++ b/public_html/inc_functions.php @@ -3,13 +3,13 @@ /** * Check authorization in the http request header and age of timestamp - * On a failed check the request will be terminated - * @global int $iMaxAge max allowed age - * @param type $sMySecret - * @return boolean + * On a failed check the request will be terminated; on suceess it returns + * the hash + * @param string $sMySecret server api key (from config file) + * @param int $iMaxAge max allowed age in [sec] + * @return string */ -function _checkAuth($sMySecret){ - global $iMaxAge; +function _checkAuth($sMySecret, $iMaxAge=60){ $aReqHeaders=apache_request_headers(); _wd('request headers: <pre>'.print_r($aReqHeaders, 1).'</pre>'); if(!isset($aReqHeaders['Authorization'])){ @@ -37,11 +37,63 @@ function _checkAuth($sMySecret){ $iAge=date('U')-date('U', strtotime($sGotDate)); _wd('Date: '.$sGotDate.' - age: '.$iAge.' sec'); if($iAge>$iMaxAge){ - _quit('Access denied. Hash is out of date: '.$iAge. ' sec is older '.$iMaxAge.' sec. Maybe client or server is out of sync.', 403); + _quit('Access denied. Hash is out of date: '.$iAge. ' sec is older '.$iMaxAge.' sec. Maybe client or server time is out of sync.', 403); } if($iAge<-$iMaxAge){ _quit('Access denied. Hash is '.$iAge. ' sec in future but only '.$iMaxAge.' sec are allowed. Maybe client or server is out of sync.', 403); } + return $sMyHash; +} + +/** + * check if a given secret was used already. + * It is for hardening - allow one time usage of a hash + * @param string $lockfile filename of lockfile with stored hashes + * @param string $sMyHash hash to verify + * @return boolean + */ +function _checkIfHashWasUsedAlready($lockfile, $sMyHash){ + $bFound=false; + $handle = @fopen($lockfile, "r"); + if ($handle){ + while (!feof($handle)) + { + $buffer = fgets($handle); + if(strstr($buffer, $sMyHash)){ + $bFound = true; + } + } + fclose($handle); + } + return $bFound; +} + +/** + * helper function for enabled one time secret: start cleanup of storage with + * used keys if a given filesize was reached. It keeps entries younger a given + * max age (same max age like in _checkAuth() + * @param string $lockfile filename of lockfile with stored hashes + * @param integer $iMaxLockfilesize size in byte when to start garbage collection + * @param integer $iMaxAge max allowed age in [sec] + * @return boolean + */ +function _cleanupLockdata($lockfile, $iMaxLockfilesize, $iMaxAge){ + if (filesize($lockfile)<$iMaxLockfilesize){ + return false; + } + $sLockdata=''; + $handle = @fopen($lockfile, "r"); + if ($handle){ + while (!feof($handle)) { + $buffer = fgets($handle); + $iTimestamp=(int)preg_replace('/\-.*$/', '', $buffer); + if ($iTimestamp && date('U') - $iTimestamp < $iMaxAge){ + $sLockdata.=$buffer; + } + } + fclose($handle); + file_put_contents($lockfile, $sLockdata); + } return true; } @@ -69,7 +121,6 @@ function _quit($s, $iStatus=400){ 404=>'HTTP/1.0 404 Not found', ); header($aStatus[$iStatus]); - # _done(array('status'=>$iStatus, 'info'=>$aStatus[$iStatus], 'message'=>$s)); _sendHtml($aStatus[$iStatus], $s); die(); } diff --git a/public_html/packages/index.php b/public_html/packages/index.php index 7f35bd464170c19cbc51bc7cdeddf9c336d70aa6..714190298c81e96095519b608b6b7899b4d1fb5e 100644 --- a/public_html/packages/index.php +++ b/public_html/packages/index.php @@ -15,11 +15,12 @@ ini_set('display_startup_errors', 1); error_reporting(E_ALL); - require('../inc_functions.php'); + require_once('../inc_functions.php'); $aConfig=require_once("../inc_config.php"); - $iMaxAge=60; - + $lockfile=$aConfig['tmpdir'].'/used_hashes.txt'; + $iMaxAge=$aConfig['maxage']; + // ---------------------------------------------------------------------- // MAIN @@ -32,20 +33,36 @@ _wd('request uri is '.$_SERVER["REQUEST_URI"]); _wd('<pre>GET: '.print_r($_GET, 1).'</pre>'); - - _checkAuth($aConfig['apikey']); - + // verify hashed secret + $sMyHash=_checkAuth($aConfig['apikey'], $iMaxAge); // if I am here then authentication was successful. + // limit to one time usage of a hash + if($aConfig['onetimesecret']){ + if(_checkIfHashWasUsedAlready($lockfile, $sMyHash)) { + _quit('Access denied. The hashed was used already.'); + } + _cleanupLockdata($lockfile, $aConfig['maxlockfilesize'], $iMaxAge); + + // first item must be unix ts followed by "-" char ... see + // _cleanupLockdata() to detect outdated data lines + file_put_contents($lockfile, date('U').'-'.date('Y-m-d__H:i:s').'-'.$sMyHash."\n", FILE_APPEND); + } + // ---------- SPLIT URL $sRelfile=preg_replace('#^/packages#', '', $_SERVER["REQUEST_URI"]); _wd('$sRelfile: '.$sRelfile); + // prevent going up a directory + if (strstr($sRelfile, '..')){ + _quit('Bad request. Invalid access to [..].', 400); + } $sMyFile=$aConfig['packagedir'].$sRelfile; _wd('full path of file: '.$sMyFile); + // handle a requested directory if (is_dir($sMyFile)){ if(!$aConfig['showdircontent']){ _quit('Filelisting is denied by config.', 403); diff --git a/readme.md b/readme.md index 47fd255bbe4c65ba3c0a6be1e48d5e540f084e34..1c97abbbd223742cbe6c62a215edad77787ba0c4 100644 --- a/readme.md +++ b/readme.md @@ -121,6 +121,7 @@ OPTIONS: -d enable debug infos -e PHASE phase; overrides env variable IMLCI_PHASE -f FILE filename to get (without path); overrides env variable IMLCI_FILE + -l ITEM list -o OUTFILE optional output file -p PROJECT ci project id; overrides env variable IMLCI_PROJECT -s SECRET override secret in IMLCI_PKG_SECRET @@ -135,6 +136,9 @@ VALUES: PROJECT project id of the ci server SECRET secret to access project data on package server. Your given secret must match the secret on package server to get access to any url. + ITEM type what to list; one of phases|projects|files + To list projects a phase must be set. + To list files a phase and a project must be set. DEFAULTS: @@ -156,11 +160,11 @@ EXAMPLES: there is a special file ALL; it fetches all filenames by executing a directory listing and then downloads all remote files with their original name - getfile.sh -f '' - empty file = directory listing of all your project files - - getfile.sh -p '' - empty project = directory listing of all projects with current phase + getfile.sh -e preview -l projects + list existing projects in phase preview + + getfile.sh -l files + list existing files of current project Remark: The directory listing can be turned off on the package server and results in a 403 status. diff --git a/shellscripts/getfile.sh b/shellscripts/getfile.sh index f7ecb33fa3ebaa5e3ed61314c40dd6730e6e0be5..33508ce303902c8c3b85d21b13f2805b00e7c435 100755 --- a/shellscripts/getfile.sh +++ b/shellscripts/getfile.sh @@ -22,7 +22,12 @@ bDebug=0 function showhelp(){ self=$( basename $0 ) -echo "SYNTAX: +echo " +CIPGK GETTER + +Get packages from a software sattelite of IML ci server. + +SYNTAX: $self [OPTIONS] @@ -31,6 +36,7 @@ OPTIONS: -d enable debug infos -e PHASE phase; overrides env variable IMLCI_PHASE -f FILE filename to get (without path); overrides env variable IMLCI_FILE + -l ITEM list -o OUTFILE optional output file -p PROJECT ci project id; overrides env variable IMLCI_PROJECT -s SECRET override secret in IMLCI_PKG_SECRET @@ -45,7 +51,9 @@ VALUES: PROJECT project id of the ci server SECRET secret to access project data on package server. Your given secret must match the secret on package server to get access to any url. - + ITEM type what to list; one of phases|projects|files + To list projects a phase must be set. + To list files a phase and a project must be set. DEFAULTS: You don't need to set all values by command line. Use a config to set defaults @@ -66,11 +74,11 @@ EXAMPLES: there is a special file ALL; it fetches all filenames by executing a directory listing and then downloads all remote files with their original name - $self -f '' - empty file = directory listing of all your project files - - $self -p '' - empty project = directory listing of all projects with current phase + $self -e preview -l projects + list existing projects in phase preview + + $self -l files + list existing files of current project Remark: The directory listing can be turned off on the package server and results in a 403 status. @@ -118,12 +126,16 @@ ${apiTS} -X $apiMethod \ -o "${outfile}.downloading" \ --fail \ + -s \ ${IMLCI_URL}${apiRequest} + if [ $? -eq 0 ]; then - echo Download OK. + # echo OK. + + # no outfile (= request to a directory) if [ -z "$outfile" ]; then - echo - echo ----- RESPONSE BODY: + # echo + # echo ----- RESPONSE BODY: cat "${outfile}.downloading" rm -f "${outfile}.downloading" else @@ -148,21 +160,37 @@ ${apiTS} # MAIN # ---------------------------------------------------------------------- -echo -echo ===== CIPGK GETTER :: `date` ===== -echo - if [ $# -lt 1 ]; then showhelp exit 1 fi -while getopts "de:f:o:p:s:u:" option; do +while getopts "de:f:l:o:p:s:u:" option; do case ${option} in d) bDebug=1 ;; e) export IMLCI_PHASE=$OPTARG ;; f) export IMLCI_FILE=$OPTARG ;; + l) case $OPTARG in + phases) + IMLCI_PHASE='' + IMLCI_PROJECT='' + IMLCI_FILE='' + ;; + projects) + IMLCI_PROJECT='' + IMLCI_FILE='' + ;; + files) + IMLCI_FILE='' + ;; + *) + echo ERROR: invalid value for option [-l] + echo + showhelp + exit 2 + esac + ;; o) export IMLCI_OUTFILE=$OPTARG ;; p) export IMLCI_PROJECT=$OPTARG ;; s) export IMLCI_PKG_SECRET=$OPTARG ;; @@ -200,21 +228,22 @@ if [ $bDebug = 1 ]; then fi if [ "$IMLCI_FILE" = "ALL" ]; then - echo ALL files were requested ... - echo Get filelist ... - tmpfilelist=/tmp/myfilelist_gdgsdgadg + # echo ALL files were requested ... + printf "%-30s" "get list of all files... " + tmpfilelist=$( mktemp ) $0 -u "${IMLCI_URL}" \ -p "${IMLCI_PROJECT}" \ -e "${IMLCI_PHASE}" \ -s "${IMLCI_PKG_SECRET}" \ + -l files \ -o "${tmpfilelist}" - cat "${tmpfilelist}" + # cat "${tmpfilelist}" cat "${tmpfilelist}" | grep "^file:" | while read fileline do - echo $line + # echo $line myfile=$( echo $fileline | cut -f 2- -d ':' ) - echo GET $myfile... + printf "%-30s" "GET $myfile... " $0 -u "${IMLCI_URL}" \ -p "${IMLCI_PROJECT}" \ -e "${IMLCI_PHASE}" \ diff --git a/tmp/.htkeep b/tmp/.htkeep new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/tmp/.htkeep @@ -0,0 +1 @@ +