1#!/usr/bin/env bash 2 3# shellcheck disable=2034 4GITSECRET_VERSION='0.2.5' 5#!/usr/bin/env bash 6 7# Folders: 8_SECRETS_DIR=${SECRETS_DIR:-".gitsecret"} 9# if SECRETS_DIR env var is set, use that instead of .gitsecret 10# for full path to secrets dir, use _get_secrets_dir() from _git_secret_tools.sh 11_SECRETS_DIR_KEYS="${_SECRETS_DIR}/keys" 12_SECRETS_DIR_PATHS="${_SECRETS_DIR}/paths" 13 14# Files: 15_SECRETS_DIR_KEYS_MAPPING="${_SECRETS_DIR_KEYS}/mapping.cfg" 16_SECRETS_DIR_KEYS_TRUSTDB="${_SECRETS_DIR_KEYS}/trustdb.gpg" 17 18_SECRETS_DIR_PATHS_MAPPING="${_SECRETS_DIR_PATHS}/mapping.cfg" 19 20: "${SECRETS_EXTENSION:=".secret"}" 21 22# Commands: 23: "${SECRETS_GPG_COMMAND:="gpg"}" 24: "${SECRETS_CHECKSUM_COMMAND:="_os_based __sha256"}" 25: "${SECRETS_OCTAL_PERMS_COMMAND:="_os_based __get_octal_perms"}" 26: "${SECRETS_EPOCH_TO_DATE:="_os_based __epoch_to_date"}" 27 28 29# AWK scripts: 30# shellcheck disable=2016 31AWK_FSDB_HAS_RECORD=' 32BEGIN { FS=":"; OFS=":"; cnt=0; } 33{ 34 if ( key == $1 ) 35 { 36 cnt++ 37 } 38} 39END { if ( cnt > 0 ) print "0"; else print "1"; } 40' 41 42# shellcheck disable=2016 43AWK_FSDB_RM_RECORD=' 44BEGIN { FS=":"; OFS=":"; } 45{ 46 if ( key != $1 ) 47 { 48 print $1,$2; 49 } 50} 51' 52 53# shellcheck disable=2016 54AWK_FSDB_CLEAR_HASHES=' 55BEGIN { FS=":"; OFS=":"; } 56{ 57 print $1,""; 58} 59' 60 61# shellcheck disable=2016 62AWK_GPG_VER_CHECK=' 63/^gpg/{ 64 version=$3 65 n=split(version,array,".") 66 if( n >= 2) { 67 if(array[1] >= 2) 68 { 69 if(array[2] >= 1) 70 { 71 print 1 72 } 73 else 74 { 75 print 0 76 } 77 } 78 else 79 { 80 print 0 81 } 82 } 83 else if(array[1] >= 2) 84 { 85 print 1 86 } 87 else 88 { 89 print 0 90 } 91} 92' 93 94# This is 1 for gpg version 2.1 or greater, otherwise 0 95GPG_VER_21="$(gpg --version | gawk "$AWK_GPG_VER_CHECK")" 96 97 98# Bash: 99 100function _function_exists { 101 local function_name="$1" # required 102 103 declare -f -F "$function_name" > /dev/null 2>&1 104 echo $? 105} 106 107 108# OS based: 109 110function _os_based { 111 # Pass function name as first parameter. 112 # It will be invoked as os-based function with the postfix. 113 114 case "$(uname -s)" in 115 116 Darwin) 117 "$1_osx" "${@:2}" 118 ;; 119 120 Linux) 121 "$1_linux" "${@:2}" 122 ;; 123 124 MINGW*) 125 "$1_linux" "${@:2}" 126 ;; 127 128 FreeBSD) 129 "$1_freebsd" "${@:2}" 130 ;; 131 132 # TODO: add MS Windows support. 133 # CYGWIN*|MINGW32*|MSYS*) 134 # $1_ms ${@:2} 135 # ;; 136 137 *) 138 _abort 'unsupported OS.' 139 ;; 140 esac 141} 142 143 144# File System: 145 146function _set_config { 147 # This function creates a line in the config, or alters it. 148 149 local key="$1" # required 150 local value="$2" # required 151 local filename="$3" # required 152 153 # The exit status is 0 (true) if the name was found, 1 (false) if not: 154 local contains 155 contains=$(grep -Fq "$key" "$filename"; echo "$?") 156 157 # Append or alter? 158 if [[ "$contains" -eq 0 ]]; then 159 _os_based __replace_in_file "$@" 160 elif [[ "$contains" -eq 1 ]]; then 161 echo "${key} = ${value}" >> "$filename" 162 fi 163} 164 165 166function _file_has_line { 167 # First parameter is the key, second is the filename. 168 169 local key="$1" # required 170 local filename="$2" # required 171 172 local contains 173 contains=$(grep -qw "$key" "$filename"; echo $?) 174 175 # 0 on contains, 1 for error. 176 echo "$contains" 177} 178 179 180function _delete_line { 181 local escaped_path 182 # shellcheck disable=2001 183 escaped_path=$(echo "$1" | sed -e 's/[\/&]/\\&/g') # required 184 185 local line="$2" # required 186 187 sed -i.bak "/$escaped_path/d" "$line" 188} 189 190 191# this sets the global variable 'filename' 192# currently this function is only used by 'hide' 193function _temporary_file { 194 # This function creates temporary file 195 # which will be removed on system exit. 196 filename=$(_os_based __temp_file) # is not `local` on purpose. 197 198 trap 'echo "cleaning up..."; rm -f "$filename";' EXIT 199} 200 201 202function _unique_filename { 203 # First parameter is base-path, second is filename, 204 # third is optional extension. 205 local n=0 206 local base_path="$1" 207 local result="$2" 208 209 while true; do 210 if [[ ! -f "$base_path/$result" ]]; then 211 break 212 fi 213 214 n=$(( n + 1 )) 215 result="${2}-${n}" # calling to the original "$2" 216 done 217 echo "$result" 218} 219 220# Helper function 221 222 223function _gawk_inplace { 224 local parms="$*" 225 local dest_file 226 dest_file="$(echo "$parms" | gawk -v RS="'" -v FS="'" 'END{ gsub(/^\s+/,""); print $1 }')" 227 228 _temporary_file 229 230 bash -c "gawk ${parms}" > "$filename" 231 mv "$filename" "$dest_file" 232} 233 234 235# File System Database (fsdb): 236 237 238function _get_record_filename { 239 # Returns 1st field from passed record 240 local record="$1" 241 local filename 242 filename=$(echo "$record" | awk -F: '{print $1}') 243 244 echo "$filename" 245} 246 247 248function _get_record_hash { 249 # Returns 2nd field from passed record 250 local record="$1" 251 local hash 252 hash=$(echo "$record" | awk -F: '{print $2}') 253 254 echo "$hash" 255} 256 257 258function _fsdb_has_record { 259 # First parameter is the key 260 # Second is the fsdb 261 local key="$1" # required 262 local fsdb="$2" # required 263 264 # 0 on contains, 1 for error. 265 gawk -v key="$key" "$AWK_FSDB_HAS_RECORD" "$fsdb" 266} 267 268 269function _fsdb_rm_record { 270 # First parameter is the key (filename) 271 # Second is the path to fsdb 272 local key="$1" # required 273 local fsdb="$2" # required 274 275 _gawk_inplace -v key="'$key'" "'$AWK_FSDB_RM_RECORD'" "$fsdb" 276} 277 278function _fsdb_clear_hashes { 279 # First parameter is the path to fsdb 280 local fsdb="$1" # required 281 282 _gawk_inplace "'$AWK_FSDB_CLEAR_HASHES'" "$fsdb" 283} 284 285 286# Manuals: 287 288function _show_manual_for { 289 local function_name="$1" # required 290 291 man "git-secret-${function_name}" 292 exit 0 293} 294 295 296# Invalid options 297 298function _invalid_option_for { 299 local function_name="$1" # required 300 301 man "git-secret-${function_name}" 302 exit 1 303} 304 305 306# VCS: 307 308function _check_ignore { 309 local filename="$1" # required 310 311 local result 312 result="$(git add -n "$filename" > /dev/null 2>&1; echo $?)" 313 # when ignored 314 if [[ "$result" -ne 0 ]]; then 315 result=0 316 else 317 result=1 318 fi 319 # returns 1 when not ignored, and 0 when ignored 320 echo "$result" 321} 322 323 324function _git_normalize_filename { 325 local filename="$1" # required 326 327 local result 328 result=$(git ls-files --full-name -o "$filename") 329 echo "$result" 330} 331 332 333function _maybe_create_gitignore { 334 # This function creates '.gitignore' if it was missing. 335 336 local full_path 337 full_path=$(_append_root_path '.gitignore') 338 339 if [[ ! -f "$full_path" ]]; then 340 touch "$full_path" 341 fi 342} 343 344 345function _add_ignored_file { 346 # This function adds a line with the filename into the '.gitignore' file. 347 # It also creates '.gitignore' if it's not there 348 349 local filename="$1" # required 350 351 _maybe_create_gitignore 352 353 local full_path 354 full_path=$(_append_root_path '.gitignore') 355 356 echo "$filename" >> "$full_path" 357} 358 359 360function _is_inside_git_tree { 361 # Checks if we are working inside the `git` tree. 362 local result 363 result=$(git rev-parse --is-inside-work-tree > /dev/null 2>&1; echo $?) 364 365 echo "$result" 366} 367 368function _is_tracked_in_git { 369 local filename="$1" # required 370 local result 371 result="$(git ls-files --error-unmatch "$filename" >/dev/null 2>&1; echo $?)" 372 373 if [[ "$result" -eq 0 ]]; then 374 echo "1" 375 else 376 echo "0" 377 fi 378} 379 380 381function _get_git_root_path { 382 # We need this function to get the location of the `.git` folder, 383 # since `.gitsecret` (or value set by SECRETS_DIR env var) must be on the same level. 384 385 local result 386 result=$(git rev-parse --show-toplevel) 387 echo "$result" 388} 389 390 391# Relative paths: 392 393function _append_root_path { 394 # This function adds root path to any other path. 395 396 local path="$1" # required 397 398 local root_path 399 root_path=$(_get_git_root_path) 400 401 echo "$root_path/$path" 402} 403 404 405function _get_secrets_dir { 406 _append_root_path "${_SECRETS_DIR}" 407} 408 409 410function _get_secrets_dir_keys { 411 _append_root_path "${_SECRETS_DIR_KEYS}" 412} 413 414 415function _get_secrets_dir_path { 416 _append_root_path "${_SECRETS_DIR_PATHS}" 417} 418 419 420function _get_secrets_dir_keys_mapping { 421 _append_root_path "${_SECRETS_DIR_KEYS_MAPPING}" 422} 423 424 425function _get_secrets_dir_keys_trustdb { 426 _append_root_path "${_SECRETS_DIR_KEYS_TRUSTDB}" 427} 428 429 430function _get_secrets_dir_paths_mapping { 431 _append_root_path "${_SECRETS_DIR_PATHS_MAPPING}" 432} 433 434 435# Logic: 436 437function _abort { 438 local message="$1" # required 439 local exit_code=${2:-"1"} # defaults to 1 440 441 >&2 echo "git-secret: abort: $message" 442 exit "$exit_code" 443} 444 445# _warn() sends warnings to stdout so user sees them 446function _warn { 447 local message="$1" # required 448 449 >&2 echo "git-secret: warning: $message" 450} 451 452# _warn_or_abort "$error_message" "$exit_code" "$error_ok" 453function _warn_or_abort { 454 local message="$1" # required 455 local exit_code=${2:-"1"} # defaults to 1 456 local error_ok=${3:-0} # can be 0 or 1 457 458 if [[ "$error_ok" -eq "0" ]]; then 459 if [[ "$exit_code" -eq "0" ]]; then 460 # if caller sends an exit_code of 0, we change it to 1 before aborting. 461 exit_code=1 462 fi 463 _abort "$message" "$exit_code" 464 else 465 _warn "$message" "$exit_code" 466 fi 467} 468 469function _find_and_clean { 470 # required: 471 local pattern="$1" # can be any string pattern 472 473 # optional: 474 local verbose=${2:-""} # can be empty or should be equal to "v" 475 476 local root 477 root=$(_get_git_root_path) 478 479 # shellcheck disable=2086 480 find "$root" -path "$pattern" -type f -print0 | xargs -0 rm -f$verbose 481} 482 483 484function _find_and_clean_formatted { 485 # required: 486 local pattern="$1" # can be any string pattern 487 488 # optional: 489 local verbose=${2:-""} # can be empty or should be equal to "v" 490 local message=${3:-"cleaning:"} # can be any string 491 492 if [[ -n "$verbose" ]]; then 493 echo && echo "$message" 494 fi 495 496 _find_and_clean "$pattern" "$verbose" 497 498 if [[ -n "$verbose" ]]; then 499 echo 500 fi 501} 502 503 504# this sets the global array variable 'filenames' 505function _list_all_added_files { 506 local path_mappings 507 path_mappings=$(_get_secrets_dir_paths_mapping) 508 509 if [[ ! -s "$path_mappings" ]]; then 510 _abort "$path_mappings is missing." 511 fi 512 513 local filename 514 filenames=() # not local 515 while read -r line; do 516 filename=$(_get_record_filename "$line") 517 filenames+=("$filename") 518 done < "$path_mappings" 519 520 declare -a filenames # so caller can get list from filenames array 521} 522 523 524function _secrets_dir_exists { 525 # This function checks if "$_SECRETS_DIR" exists and. 526 527 local full_path 528 full_path=$(_get_secrets_dir) 529 530 if [[ ! -d "$full_path" ]]; then 531 local name 532 name=$(basename "$full_path") 533 _abort "directory '$name' does not exist. Use 'git secret init' to initialize git-secret" 534 fi 535} 536 537 538function _secrets_dir_is_not_ignored { 539 # This function checks that "$_SECRETS_DIR" is not ignored. 540 541 local git_secret_dir 542 git_secret_dir=$(_get_secrets_dir) 543 544 # Create git_secret_dir required for check 545 local cleanup=0 546 if [[ ! -d "$git_secret_dir" ]]; then 547 mkdir "$git_secret_dir" 548 cleanup=1 549 fi 550 local ignores 551 ignores=$(_check_ignore "$git_secret_dir") 552 if [[ "$cleanup" == 1 ]]; then 553 rmdir "$git_secret_dir" 554 fi 555 556 if [[ ! $ignores -eq 1 ]]; then 557 _abort "'$git_secret_dir' is in .gitignore" 558 fi 559} 560 561 562function _user_required { 563 # This function does a bunch of validations: 564 # 1. It calls `_secrets_dir_exists` to verify that "$_SECRETS_DIR" exists. 565 # 2. It ensures that "$_SECRETS_DIR_KEYS_TRUSTDB" exists. 566 # 3. It ensures that there are added public keys. 567 568 _secrets_dir_exists 569 570 local trustdb 571 trustdb=$(_get_secrets_dir_keys_trustdb) 572 573 local error_message="no public keys for users found. run 'git secret tell email@address'." 574 if [[ ! -f "$trustdb" ]]; then 575 _abort "$error_message" 576 fi 577 578 local secrets_dir_keys 579 secrets_dir_keys=$(_get_secrets_dir_keys) 580 581 local keys_exist 582 keys_exist=$($SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning -n --list-keys) 583 local exit_code=$? 584 if [[ "$exit_code" -ne 0 ]]; then 585 # this might catch corner case where gpg --list-keys shows 586 # 'gpg: skipped packet of type 12 in keybox' warnings but succeeds? 587 # See #136 588 _abort "problem listing public keys with gpg: exit code $exit_code" 589 fi 590 if [[ -z "$keys_exist" ]]; then 591 _abort "$error_message" 592 fi 593} 594 595# note: this has the same 'username matching' issue described in 596# https://github.com/sobolevn/git-secret/issues/268 597# where it will match emails that have other emails as substrings. 598# we need to use fingerprints for a unique key id with gpg. 599function _get_user_key_expiry { 600 # This function returns the user's key's expiry, as an epoch. 601 # It will return the empty string if there is no expiry date for the user's key 602 local username="$1" 603 local line 604 605 local secrets_dir_keys 606 secrets_dir_keys=$(_get_secrets_dir_keys) 607 608 line=$($SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning --list-public-keys --with-colon --fixed-list-mode "$username" | grep ^pub:) 609 610 local expiry_epoch 611 expiry_epoch=$(echo "$line" | cut -d: -f7) 612 echo "$expiry_epoch" 613} 614 615 616function _assert_keychain_contains_emails { 617 local homedir=$1 618 local emails=$2 619 620 local gpg_uids 621 gpg_uids=$(_get_users_in_gpg_keyring "$homedir") 622 for email in "${emails[@]}"; do 623 local email_ok=0 624 for uid in $gpg_uids; do 625 if [[ "$uid" == "$email" ]]; then 626 email_ok=1 627 fi 628 done 629 if [[ $email_ok -eq 0 ]]; then 630 _abort "email not found in gpg keyring: $email" 631 fi 632 done 633} 634 635 636function _get_raw_filename { 637 echo "$(dirname "$1")/$(basename "$1" "$SECRETS_EXTENSION")" | sed -e 's#^\./##' 638} 639 640 641function _get_encrypted_filename { 642 local filename 643 filename="$(dirname "$1")/$(basename "$1" "$SECRETS_EXTENSION")" 644 echo "${filename}${SECRETS_EXTENSION}" | sed -e 's#^\./##' 645} 646 647 648function _get_users_in_gpg_keyring { 649 # show the users in the gpg keyring. 650 # `whoknows` command uses it internally. 651 # parses the `gpg` public keys 652 local homedir=$1 653 local result 654 local args=() 655 if [[ -n "$homedir" ]]; then 656 args+=( "--homedir" "$homedir" ) 657 fi 658 659 # pluck out 'uid' lines, fetch 10th field, extract part in <> if it exists (else leave alone). 660 # we use --fixed-list-mode so older versions of gpg emit 'uid:' lines. 661 # sed at the end is to extract email from <>. (If there's no <>, then the line is just an email address anyway.) 662 result=$($SECRETS_GPG_COMMAND "${args[@]}" --no-permission-warning --list-public-keys --with-colon --fixed-list-mode | grep ^uid: | cut -d: -f10 | sed 's/.*<\(.*\)>.*/\1/') 663 664 echo "$result" 665} 666 667 668function _get_users_in_gitsecret_keyring { 669 # show the users in the gitsecret keyring. 670 local secrets_dir_keys 671 secrets_dir_keys=$(_get_secrets_dir_keys) 672 673 local result 674 result=$(_get_users_in_gpg_keyring "$secrets_dir_keys") 675 676 echo "$result" 677} 678 679 680function _get_recipients { 681 # This function is required to create an encrypted file for different users. 682 # These users are called 'recipients' in the `gpg` terms. 683 # It basically just parses the `gpg` public keys 684 685 local result 686 result=$(_get_users_in_gitsecret_keyring | sed 's/^/-r/') # put -r before each user 687 echo "$result" 688} 689 690 691function _decrypt { 692 # required: 693 local filename="$1" 694 695 # optional: 696 local write_to_file=${2:-1} # can be 0 or 1 697 local force=${3:-0} # can be 0 or 1 698 local homedir=${4:-""} 699 local passphrase=${5:-""} 700 local error_ok=${6:-0} # can be 0 or 1 701 702 local encrypted_filename 703 encrypted_filename=$(_get_encrypted_filename "$filename") 704 705 local args=( "--use-agent" "--decrypt" "--no-permission-warning" ) 706 707 if [[ "$write_to_file" -eq 1 ]]; then 708 args+=( "-o" "$filename" ) 709 fi 710 711 if [[ "$force" -eq 1 ]]; then 712 args+=( "--yes" ) 713 fi 714 715 if [[ -n "$homedir" ]]; then 716 args+=( "--homedir" "$homedir" ) 717 fi 718 719 if [[ "$GPG_VER_21" -eq 1 ]]; then 720 args+=( "--pinentry-mode" "loopback" ) 721 fi 722 723 set +e # disable 'set -e' so we can capture exit_code 724 725 #echo "# gpg passphrase: $passphrase" >&3 726 local exit_code 727 if [[ -n "$passphrase" ]]; then 728 echo "$passphrase" | $SECRETS_GPG_COMMAND "${args[@]}" --quiet --batch --yes --no-tty --passphrase-fd 0 \ 729 "$encrypted_filename" 730 exit_code=$? 731 else 732 $SECRETS_GPG_COMMAND "${args[@]}" "--quiet" "$encrypted_filename" 733 exit_code=$? 734 fi 735 736 set -e # re-enable set -e 737 738 # note that according to https://github.com/sobolevn/git-secret/issues/238 , 739 # it's possible for gpg to return a 0 exit code but not have decrypted the file 740 #echo "# gpg exit code: $exit_code, error_ok: $error_ok" >&3 741 if [[ "$exit_code" -ne "0" ]]; then 742 local msg="problem decrypting file with gpg: exit code $exit_code: $filename" 743 _warn_or_abort "$msg" "$exit_code" "$error_ok" 744 fi 745 746 # at this point the file should be written to disk or output to stdout 747} 748 749#!/usr/bin/env bash 750 751# support for freebsd. Mostly the same as OSX. 752 753 754# shellcheck disable=1117 755function __replace_in_file_freebsd { 756 sed -i.bak "s/^\($1[[:space:]]*=[[:space:]]*\).*\$/\1$2/" "$3" 757} 758 759 760function __temp_file_freebsd { 761 : "${TMPDIR:=/tmp}" 762 local filename 763 filename=$(mktemp -t _gitsecrets_XXX ) 764 echo "$filename"; 765} 766 767 768function __sha256_freebsd { 769 # this is in a different location than osx 770 /usr/local/bin/shasum -a256 "$1" 771} 772 773function __get_octal_perms_freebsd { 774 local filename 775 filename=$1 776 local perms 777 perms=$(stat -f "%04OLp" "$filename") 778 # perms is a string like '0644'. 779 # In the "%04OLp': 780 # the '04' means 4 digits, 0 padded. So we get 0644, not 644. 781 # the 'O' means Octal. 782 # the 'Lp' means 'low subfield of file type and permissions (st_mode).' 783 # (without 'L' you get 6 digits like '100644'.) 784 echo "$perms" 785} 786 787function __epoch_to_date_freebsd { 788 local epoch=$1; 789 if [ -z "$epoch" ]; then 790 echo '' 791 else 792 local cmd="date +%F -r $epoch" 793 local datetime 794 datetime=$($cmd) 795 echo "$datetime" 796 fi 797} 798#!/usr/bin/env bash 799 800 801# shellcheck disable=1117 802function __replace_in_file_linux { 803 sed -i.bak "s/^\($1\s*=\s*\).*\$/\1$2/" "$3" 804} 805 806 807function __temp_file_linux { 808 local filename 809 filename=$(mktemp) 810 echo "$filename" 811} 812 813function __sha256_linux { 814 sha256sum "$1" 815} 816 817function __get_octal_perms_linux { 818 local filename 819 filename=$1 820 local perms 821 perms=$(stat --format '%a' "$filename") 822 # a string like '0644' 823 echo "$perms" 824} 825 826function __epoch_to_date_linux { 827 local epoch=$1; 828 if [ -z "$epoch" ]; then 829 echo '' 830 else 831 local cmd="date +%F -d @$epoch" 832 local datetime 833 datetime=$($cmd) 834 echo "$datetime" 835 fi 836} 837#!/usr/bin/env bash 838 839 840# shellcheck disable=1117 841function __replace_in_file_osx { 842 sed -i.bak "s/^\($1[[:space:]]*=[[:space:]]*\).*\$/\1$2/" "$3" 843} 844 845 846function __temp_file_osx { 847 : "${TMPDIR:=/tmp}" 848 local filename 849 filename=$(mktemp -t _gitsecrets_XXX ) 850 echo "$filename"; 851} 852 853 854function __sha256_osx { 855 /usr/bin/shasum -a256 "$1" 856} 857 858function __get_octal_perms_osx { 859 local filename 860 filename=$1 861 local perms 862 perms=$(stat -f "%04OLp" "$filename") 863 # see _git_secret_tools_freebsd.sh for more about stat's format string 864 echo "$perms" 865} 866 867function __epoch_to_date_osx { 868 local epoch=$1; 869 if [ -z "$epoch" ]; then 870 echo '' 871 else 872 #date -r 234234234 +"%Y-%m-%d" 873 local datetime 874 datetime=$(date -r "$epoch" +'%Y-%m-%d') 875 echo "$datetime" 876 fi 877} 878 879#!/usr/bin/env bash 880 881 882function add { 883 local auto_ignore=0 884 OPTIND=1 885 886 while getopts "ih" opt; do 887 case "$opt" in 888 i) auto_ignore=1;; 889 890 h) _show_manual_for "add";; 891 892 *) _invalid_option_for "add";; 893 esac 894 done 895 896 shift $((OPTIND-1)) 897 [ "$1" = "--" ] && shift 898 899 _user_required 900 901 # Checking if all files are correct (ignored and inside the repo): 902 903 local not_ignored=() 904 local items=( "$@" ) 905 906 # Checking if all files in options are ignored: 907 for item in "${items[@]}"; do 908 local path # absolute path 909 local normalized_path # relative to the .git dir 910 normalized_path=$(_git_normalize_filename "$item") 911 path=$(_append_root_path "$normalized_path") 912 913 # check that the file is not tracked 914 local in_git 915 in_git=$(_is_tracked_in_git "$item") 916 if [[ "$in_git" -ne 0 ]]; then 917 _abort "file tracked in git, consider using 'git rm --cached $item'" 918 fi 919 920 # Checking that file is valid: 921 if [[ ! -f "$path" ]]; then 922 _abort "file not found: $item" 923 fi 924 925 # Checking that it is ignored: 926 local ignored 927 ignored=$(_check_ignore "$path") 928 929 if [[ "$ignored" -ne 0 ]]; then 930 # Collect unignored files: 931 not_ignored+=("$normalized_path") 932 fi 933 done 934 935 # Are there any unignored files? 936 937 if [[ ! "${#not_ignored[@]}" -eq 0 ]]; then 938 # And show them all at once. 939 local message 940 message="these files are not in .gitignore: $*" 941 942 if [[ "$auto_ignore" -eq 0 ]]; then 943 # This file is not ignored. user don't want it to be added automatically. 944 # Raise the exception, since all files, which will be hidden, must be ignored. 945 _abort "$message" 946 else 947 # In this case these files should be added to the `.gitignore` automatically: 948 # see https://github.com/sobolevn/git-secret/issues/18 for more. 949 echo "$message" 950 echo "auto adding them to .gitignore" 951 for item in "${not_ignored[@]}"; do 952 _add_ignored_file "$item" 953 done 954 fi 955 fi 956 957 # Adding files to path mappings: 958 959 local fsdb 960 fsdb=$(_get_secrets_dir_paths_mapping) 961 962 for item in "${items[@]}"; do 963 local path 964 local key 965 path=$(_git_normalize_filename "$item") 966 key="$path" 967 968 # Adding files into system, skipping duplicates. 969 local already_in 970 already_in=$(_fsdb_has_record "$key" "$fsdb") 971 if [[ "$already_in" -eq 1 ]]; then 972 echo "$key" >> "$fsdb" 973 fi 974 done 975 976 echo "${#@} item(s) added." 977} 978#!/usr/bin/env bash 979 980 981function cat { 982 local homedir='' 983 local passphrase='' 984 985 OPTIND=1 986 987 while getopts 'hd:p:' opt; do 988 case "$opt" in 989 h) _show_manual_for 'cat';; 990 991 p) passphrase=$OPTARG;; 992 993 d) homedir=$OPTARG;; 994 995 *) _invalid_option_for 'cat';; 996 esac 997 done 998 999 shift $((OPTIND-1)) 1000 [ "$1" = '--' ] && shift 1001 1002 _user_required 1003 1004 # Command logic: 1005 1006 for line in "$@" 1007 do 1008 local filename 1009 local path 1010 1011 filename=$(_get_record_filename "$line") 1012 path=$(_append_root_path "$filename") 1013 1014 # The parameters are: filename, write-to-file, force, homedir, passphrase 1015 _decrypt "$path" "0" "0" "$homedir" "$passphrase" 1016 done 1017} 1018#!/usr/bin/env bash 1019 1020function changes { 1021 local passphrase="" 1022 1023 OPTIND=1 1024 1025 while getopts 'hd:p:' opt; do 1026 case "$opt" in 1027 h) _show_manual_for 'changes';; 1028 1029 p) passphrase=$OPTARG;; 1030 1031 d) homedir=$OPTARG;; 1032 1033 *) _invalid_option_for 'changes';; 1034 esac 1035 done 1036 1037 shift $((OPTIND-1)) 1038 [ "$1" = '--' ] && shift 1039 1040 _user_required 1041 1042 filenames=("$@") # list of positional params. global. 1043 if [[ ${#filenames[@]} -eq 0 ]]; then 1044 # Checking if no filenames are passed, show diff for all files. 1045 _list_all_added_files # this sets the array variable 'filenames' 1046 fi 1047 1048 IFS=' 1049 ' 1050 1051 for filename in "${filenames[@]}"; do 1052 local path # absolute path 1053 local normalized_path # relative to the .git dir 1054 local encrypted_filename 1055 normalized_path=$(_git_normalize_filename "$filename") 1056 encrypted_filename=$(_get_encrypted_filename "$filename") 1057 1058 if [[ ! -f "$encrypted_filename" ]]; then 1059 _abort "cannot find encrypted version of file: $filename" 1060 fi 1061 if [[ -n "$normalized_path" ]]; then 1062 path=$(_append_root_path "$normalized_path") 1063 else 1064 # Path was already normalized 1065 path=$(_append_root_path "$filename") 1066 fi 1067 1068 if [[ ! -f "$path" ]]; then 1069 _abort "file not found. Consider using 'git secret reveal': $filename" 1070 fi 1071 1072 # Now we have all the data required to do the last encryption and compare results: 1073 # now do a two-step to protect trailing newlines from the $() construct. 1074 local decrypted_x 1075 local decrypted 1076 decrypted_x=$(_decrypt "$path" "0" "0" "$homedir" "$passphrase"; echo x$?) 1077 decrypted="${decrypted_x%x*}" 1078 # we ignore the exit code because _decrypt will _abort if appropriate. 1079 1080 1081 echo "changes in ${path}:" 1082 # diff the result: 1083 # we have the '|| true' because `diff` returns error code if files differ. 1084 diff -u <(echo -n "$decrypted") "$path" || true 1085 done 1086} 1087#!/usr/bin/env bash 1088 1089 1090function clean { 1091 local verbose='' 1092 1093 OPTIND=1 1094 1095 while getopts 'vh' opt; do 1096 case "$opt" in 1097 v) verbose="v";; 1098 1099 h) _show_manual_for 'clean';; 1100 1101 *) _invalid_option_for 'clean';; 1102 esac 1103 done 1104 1105 shift $((OPTIND-1)) 1106 [ "$1" = '--' ] && shift 1107 1108 _user_required 1109 1110 # User should see properly formatted output: 1111 _find_and_clean_formatted "*$SECRETS_EXTENSION" "$verbose" 1112} 1113#!/usr/bin/env bash 1114 1115# shellcheck disable=2016 1116AWK_FSDB_UPDATE_HASH=' 1117BEGIN { FS=":"; OFS=":"; } 1118{ 1119 if ( key == $1 ) 1120 { 1121 print key,hash; 1122 } 1123 else 1124 { 1125 print $1,$2; 1126 } 1127} 1128' 1129 1130function _optional_clean { 1131 local clean="$1" 1132 local verbose=${2:-""} 1133 1134 if [[ $clean -eq 1 ]]; then 1135 _find_and_clean_formatted "*$SECRETS_EXTENSION" "$verbose" 1136 fi 1137} 1138 1139 1140function _optional_delete { 1141 local delete="$1" 1142 local verbose=${2:-""} 1143 1144 if [[ $delete -eq 1 ]]; then 1145 local path_mappings 1146 path_mappings=$(_get_secrets_dir_paths_mapping) 1147 1148 # We use custom formatting here: 1149 if [[ -n "$verbose" ]]; then 1150 echo && echo 'removing unencrypted files:' 1151 fi 1152 1153 while read -r line; do 1154 # So the formatting would not be repeated several times here: 1155 local filename 1156 filename=$(_get_record_filename "$line") 1157 _find_and_clean "*$filename" "$verbose" 1158 done < "$path_mappings" 1159 1160 if [[ -n "$verbose" ]]; then 1161 echo 1162 fi 1163 fi 1164} 1165 1166function _get_checksum_local { 1167 local checksum="$SECRETS_CHECKSUM_COMMAND" 1168 echo "$checksum" 1169} 1170 1171function _get_file_hash { 1172 local input_path="$1" # Required 1173 local checksum_local 1174 local file_hash 1175 1176 checksum_local="$(_get_checksum_local)" 1177 file_hash=$($checksum_local "$input_path" | gawk '{print $1}') 1178 1179 echo "$file_hash" 1180} 1181 1182function _optional_fsdb_update_hash { 1183 local key="$1" 1184 local hash="$2" 1185 local fsdb # path_mappings 1186 1187 fsdb=$(_get_secrets_dir_paths_mapping) 1188 1189 _gawk_inplace -v key="'$key'" -v hash="$hash" "'$AWK_FSDB_UPDATE_HASH'" "$fsdb" 1190} 1191 1192 1193function hide { 1194 local clean=0 1195 local preserve=0 1196 local delete=0 1197 local fsdb_update_hash=0 # add checksum hashes to fsdb 1198 local verbose='' 1199 local force_continue=0 1200 1201 OPTIND=1 1202 1203 while getopts 'cFPdmvh' opt; do 1204 case "$opt" in 1205 c) clean=1;; 1206 1207 F) force_continue=1;; 1208 1209 P) preserve=1;; 1210 1211 d) delete=1;; 1212 1213 m) fsdb_update_hash=1;; 1214 1215 v) verbose='v';; 1216 1217 h) _show_manual_for 'hide';; 1218 1219 *) _invalid_option_for 'hide';; 1220 esac 1221 done 1222 1223 shift $((OPTIND-1)) 1224 [ "$1" = '--' ] && shift 1225 1226 # We need user to continue: 1227 _user_required 1228 1229 # If -c option was provided, it would clean the hidden files 1230 # before creating new ones. 1231 _optional_clean "$clean" "$verbose" 1232 1233 # Encrypting files: 1234 1235 local path_mappings 1236 path_mappings=$(_get_secrets_dir_paths_mapping) 1237 local num_mappings 1238 num_mappings=$(gawk 'END{print NR}' "$path_mappings") 1239 1240 # make sure all the unencrypted files needed are present 1241 local to_hide=() 1242 while read -r record; do 1243 to_hide+=("$record") # add record to array 1244 done < "$path_mappings" 1245 1246 local counter=0 1247 for record in "${to_hide[@]}"; do 1248 local filename 1249 local fsdb_file_hash 1250 local encrypted_filename 1251 filename=$(_get_record_filename "$record") 1252 fsdb_file_hash=$(_get_record_hash "$record") 1253 encrypted_filename=$(_get_encrypted_filename "$filename") 1254 1255 local recipients 1256 recipients=$(_get_recipients) 1257 1258 local secrets_dir_keys 1259 secrets_dir_keys=$(_get_secrets_dir_keys) 1260 1261 local input_path 1262 local output_path 1263 input_path=$(_append_root_path "$filename") 1264 output_path=$(_append_root_path "$encrypted_filename") 1265 1266 # Checking that file is valid: 1267 if [[ ! -f "$input_path" ]]; then 1268 # this catches the case where some decrypted files don't exist 1269 _warn_or_abort "file not found: $input_path" "1" "$force_continue" 1270 else 1271 file_hash=$(_get_file_hash "$input_path") 1272 1273 # encrypt file only if required 1274 if [[ "$fsdb_file_hash" != "$file_hash" ]]; then 1275 1276 set +e # disable 'set -e' so we can capture exit_code 1277 1278 # we depend on $recipients being split on whitespace 1279 # shellcheck disable=SC2086 1280 $SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" "--no-permission-warning" --use-agent --yes --trust-model=always --encrypt \ 1281 $recipients -o "$output_path" "$input_path" > /dev/null 2>&1 1282 local exit_code=$? 1283 1284 set -e # re-enable set -e 1285 1286 if [[ "$exit_code" -ne 0 ]] || [[ ! -f "$output_path" ]]; then 1287 # if gpg can't encrypt a file we asked it to, that's an error unless in force_continue mode. 1288 _warn_or_abort "problem encrypting file with gpg: exit code $exit_code: $filename" "$exit_code" "$force_continue" 1289 fi 1290 if [[ -f "$output_path" ]]; then 1291 counter=$((counter+1)) 1292 if [[ "$preserve" == 1 ]]; then 1293 local perms 1294 perms=$($SECRETS_OCTAL_PERMS_COMMAND "$input_path") 1295 chmod "$perms" "$output_path" 1296 fi 1297 fi 1298 1299 # If -m option was provided, it will update unencrypted file hash 1300 local key="$filename" 1301 local hash="$file_hash" 1302 # Update file hash if required in fsdb 1303 [[ "$fsdb_update_hash" -gt 0 ]] && \ 1304 _optional_fsdb_update_hash "$key" "$hash" 1305 fi 1306 fi 1307 done 1308 1309 # If -d option was provided, it would delete the source files 1310 # after we have already hidden them. 1311 _optional_delete "$delete" "$verbose" 1312 1313 echo "done. $counter of $num_mappings files are hidden." 1314} 1315#!/usr/bin/env bash 1316 1317# shellcheck disable=2016 1318AWK_ADD_TO_GITIGNORE=' 1319BEGIN { 1320 cnt=0 1321} 1322 1323function check_print_line(line){ 1324 if (line == pattern) { 1325 cnt++ 1326 } 1327 print line 1328} 1329 1330# main function 1331{ 1332 check_print_line($0) # check and print first line 1333 while (getline == 1) { # check and print all other 1334 check_print_line($0) 1335 } 1336} 1337 1338END { 1339 if ( cnt == 0) { # if file did not contain pattern add 1340 print pattern 1341 } 1342} 1343' 1344 1345function gitignore_add_pattern { 1346 local pattern 1347 local gitignore_file_path 1348 1349 pattern="$1" 1350 gitignore_file_path=$(_append_root_path '.gitignore') 1351 1352 _maybe_create_gitignore 1353 _gawk_inplace -v pattern="$pattern" "'$AWK_ADD_TO_GITIGNORE'" "$gitignore_file_path" 1354} 1355 1356function init { 1357 OPTIND=1 1358 1359 while getopts 'h' opt; do 1360 case "$opt" in 1361 h) _show_manual_for 'init';; 1362 1363 *) _invalid_option_for 'init';; 1364 esac 1365 done 1366 1367 shift $((OPTIND-1)) 1368 [ "$1" = '--' ] && shift 1369 1370 # Check if '.gitsecret/' already exists: 1371 local git_secret_dir 1372 git_secret_dir=$(_get_secrets_dir) 1373 1374 if [[ -d "$git_secret_dir" ]]; then 1375 _abort 'already inited.' 1376 fi 1377 1378 # Check if it is ignored: 1379 _secrets_dir_is_not_ignored 1380 1381 # Create internal files: 1382 1383 mkdir "$git_secret_dir" "$(_get_secrets_dir_keys)" "$(_get_secrets_dir_path)" 1384 touch "$(_get_secrets_dir_keys_mapping)" "$(_get_secrets_dir_paths_mapping)" 1385 1386 echo "'$git_secret_dir/' created." 1387 1388 local random_seed_file 1389 random_seed_file=".gitsecret/keys/random_seed" 1390 gitignore_add_pattern "$random_seed_file" 1391 1392 # TODO: git attributes to view diffs 1393} 1394#!/usr/bin/env bash 1395 1396 1397function killperson { 1398 OPTIND=1 1399 1400 while getopts 'h' opt; do 1401 case "$opt" in 1402 h) _show_manual_for 'killperson';; 1403 1404 *) _invalid_option_for 'killperson';; 1405 esac 1406 done 1407 1408 shift $((OPTIND-1)) 1409 [ "$1" = "--" ] && shift 1410 1411 _user_required 1412 1413 # Command logic: 1414 1415 local emails=( "$@" ) 1416 1417 if [[ ${#emails[@]} -eq 0 ]]; then 1418 _abort "at least one email is required for killperson." 1419 fi 1420 # Getting the local git-secret `gpg` key directory: 1421 local secrets_dir_keys 1422 secrets_dir_keys=$(_get_secrets_dir_keys) 1423 1424 _assert_keychain_contains_emails "$secrets_dir_keys" "${emails[@]}" 1425 1426 for email in "${emails[@]}"; do 1427 $SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning --batch --yes --delete-key "$email" 1428 local exit_code=$? 1429 if [[ "$exit_code" -ne 0 ]]; then 1430 _abort "problem deleting key for '$email' with gpg: exit code $exit_code" 1431 fi 1432 done 1433 1434 echo 'removed keys.' 1435 echo "now [$*] do not have an access to the repository." 1436 echo 'make sure to hide the existing secrets again.' 1437} 1438#!/usr/bin/env bash 1439 1440 1441function list { 1442 OPTIND=1 1443 1444 while getopts 'h' opt; do 1445 case "$opt" in 1446 h) _show_manual_for 'list';; 1447 1448 *) _invalid_option_for 'list';; 1449 esac 1450 done 1451 1452 shift $((OPTIND-1)) 1453 [ "$1" = '--' ] && shift 1454 1455 _user_required 1456 1457 # Command logic: 1458 filenames=() 1459 _list_all_added_files # exports 'filenames' array 1460 local filename 1461 for filename in "${filenames[@]}"; do 1462 echo "$filename" 1463 done 1464} 1465#!/usr/bin/env bash 1466 1467 1468function remove { 1469 local clean=0 1470 1471 OPTIND=1 1472 1473 while getopts 'ch' opt; do 1474 case "$opt" in 1475 c) clean=1;; 1476 1477 h) _show_manual_for 'remove';; 1478 1479 *) _invalid_option_for 'remove';; 1480 esac 1481 done 1482 1483 shift $((OPTIND-1)) 1484 [ "$1" = '--' ] && shift 1485 1486 # Validate if user exists: 1487 _user_required 1488 1489 # Command logic: 1490 1491 local path_mappings 1492 path_mappings=$(_get_secrets_dir_paths_mapping) 1493 1494 for item in "$@"; do 1495 local path # absolute path 1496 local normalized_path # relative to .git folder 1497 normalized_path=$(_git_normalize_filename "$item") 1498 path=$(_append_root_path "$normalized_path") 1499 1500 # Checking if file exists: 1501 if [[ ! -f "$path" ]]; then 1502 _abort "file not found: $item" 1503 fi 1504 1505 # Deleting it from path mappings: 1506 # Remove record from fsdb with matching key 1507 local key 1508 key="$normalized_path" 1509 fsdb="$path_mappings" 1510 _fsdb_rm_record "$key" "$fsdb" 1511 1512 rm -f "${path_mappings}.bak" # not all systems create '.bak' 1513 1514 # Optional clean: 1515 if [[ "$clean" -eq 1 ]]; then 1516 local encrypted_filename 1517 encrypted_filename=$(_get_encrypted_filename "$path") 1518 1519 rm "$encrypted_filename" # fail on error 1520 fi 1521 done 1522 1523 echo 'removed from index.' 1524 echo "ensure that files: [$*] are now not ignored." 1525} 1526#!/usr/bin/env bash 1527 1528 1529function reveal { 1530 local homedir='' 1531 local passphrase='' 1532 local force=0 # this means 'clobber without warning' 1533 local force_continue=0 # this means 'continue if we have decryption errors' 1534 local preserve=0 1535 1536 OPTIND=1 1537 1538 while getopts 'hfFPd:p:' opt; do 1539 case "$opt" in 1540 h) _show_manual_for 'reveal';; 1541 1542 f) force=1;; 1543 1544 F) force_continue=1;; 1545 1546 P) preserve=1;; 1547 1548 p) passphrase=$OPTARG;; 1549 1550 d) homedir=$OPTARG;; 1551 1552 *) _invalid_option_for 'reveal';; 1553 esac 1554 done 1555 1556 shift $((OPTIND-1)) 1557 [ "$1" = '--' ] && shift 1558 1559 _user_required 1560 1561 # Command logic: 1562 1563 local path_mappings 1564 path_mappings=$(_get_secrets_dir_paths_mapping) 1565 1566 local counter=0 1567 local to_show=( "$@" ) 1568 1569 if [ ${#to_show[@]} -eq 0 ]; then 1570 while read -r record; do 1571 to_show+=("$record") # add record to array 1572 done < "$path_mappings" 1573 fi 1574 1575 for line in "${to_show[@]}"; do 1576 local filename 1577 local path 1578 filename=$(_get_record_filename "$line") 1579 path=$(_append_root_path "$filename") 1580 1581 # The parameters are: filename, write-to-file, force, homedir, passphrase, error_ok 1582 _decrypt "$path" "1" "$force" "$homedir" "$passphrase" "$force_continue" 1583 1584 if [[ ! -f "$path" ]]; then 1585 _warn_or_abort "cannot find decrypted version of file: $filename" "2" "$force_continue" 1586 else 1587 counter=$((counter+1)) 1588 local secret_file 1589 secret_file=$(_get_encrypted_filename "$path") 1590 if [[ "$preserve" == 1 ]] && [[ -f "$secret_file" ]]; then 1591 local perms 1592 perms=$($SECRETS_OCTAL_PERMS_COMMAND "$secret_file") 1593 chmod "$perms" "$path" 1594 fi 1595 fi 1596 1597 done < "$path_mappings" 1598 1599 echo "done. $counter of ${#to_show[@]} files are revealed." 1600} 1601#!/usr/bin/env bash 1602 1603# shellcheck disable=2016 1604AWK_GPG_KEY_CNT=' 1605BEGIN { cnt=0; OFS=":"; FS=":"; } 1606flag=0; $1 == "pub" { cnt++ } 1607END { print cnt } 1608' 1609 1610function get_gpg_key_count { 1611 local secrets_dir_keys 1612 secrets_dir_keys=$(_get_secrets_dir_keys) 1613 $SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning --list-public-keys --with-colon | gawk "$AWK_GPG_KEY_CNT" 1614 local exit_code=$? 1615 if [[ "$exit_code" -ne 0 ]]; then 1616 _abort "problem counting keys with gpg: exit code $exit_code" 1617 fi 1618} 1619 1620function tell { 1621 local emails 1622 local self_email=0 1623 local homedir 1624 1625 # A POSIX variable 1626 # Reset in case getopts has been used previously in the shell. 1627 OPTIND=1 1628 1629 while getopts "hmd:" opt; do 1630 case "$opt" in 1631 h) _show_manual_for "tell";; 1632 1633 m) self_email=1;; 1634 1635 d) homedir=$OPTARG;; 1636 1637 *) _invalid_option_for 'tell';; 1638 esac 1639 done 1640 1641 shift $((OPTIND-1)) 1642 [ "$1" = "--" ] && shift 1643 1644 # Validates that application is inited: 1645 _secrets_dir_exists 1646 1647 # Command logic: 1648 emails=( "$@" ) 1649 local git_email 1650 1651 if [[ "$self_email" -eq 1 ]]; then 1652 git_email=$(git config user.email) 1653 1654 if [[ -z "$git_email" ]]; then 1655 _abort "'git config user.email' is not set." 1656 fi 1657 1658 emails+=("$git_email") 1659 fi 1660 1661 if [[ "${#emails[@]}" -eq 0 ]]; then 1662 # If after possible addition of git_email, emails are still empty, 1663 # we should raise an exception. 1664 _abort "you must provide at least one email address." 1665 fi 1666 1667 _assert_keychain_contains_emails "$homedir" "${emails[@]}" 1668 1669 local start_key_cnt 1670 start_key_cnt=$(get_gpg_key_count) 1671 for email in "${emails[@]}"; do 1672 # This file will be removed automatically: 1673 _temporary_file # note, that `_temporary_file` will export `filename` var. 1674 # shellcheck disable=2154 1675 local keyfile="$filename" 1676 1677 local exit_code 1678 if [[ -z "$homedir" ]]; then 1679 $SECRETS_GPG_COMMAND --export -a "$email" > "$keyfile" 1680 exit_code=$? 1681 else 1682 # It means that homedir is set as an extra argument via `-d`: 1683 $SECRETS_GPG_COMMAND --no-permission-warning --homedir="$homedir" \ 1684 --export -a "$email" > "$keyfile" 1685 exit_code=$? 1686 fi 1687 if [[ "$exit_code" -ne 0 ]]; then 1688 _abort "problem exporting public key for '$email' with gpg: exit code $exit_code" 1689 fi 1690 1691 if [[ ! -s "$keyfile" ]]; then 1692 _abort "no keyfile found for '$email'. Check your key name: 'gpg --list-keys'." 1693 fi 1694 1695 # Importing public key to the local keychain: 1696 local secrets_dir_keys 1697 secrets_dir_keys=$(_get_secrets_dir_keys) 1698 $SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning --import "$keyfile" > /dev/null 2>&1 1699 exit_code=$? 1700 if [[ "$exit_code" -ne 0 ]]; then 1701 _abort "problem importing public key for '$email' with gpg: exit code $exit_code" 1702 fi 1703 done 1704 1705 echo "done. ${emails[*]} added as someone who know(s) the secret." 1706 1707 # force re-encrypting of files if required 1708 local fsdb 1709 local end_key_cnt 1710 fsdb=$(_get_secrets_dir_paths_mapping) 1711 end_key_cnt=$(get_gpg_key_count) 1712 [[ $start_key_cnt -ne $end_key_cnt ]] && _fsdb_clear_hashes "$fsdb" 1713} 1714#!/usr/bin/env bash 1715 1716 1717function usage { 1718 OPTIND=1 1719 1720 while getopts "h?" opt; do 1721 case "$opt" in 1722 h) _show_manual_for "usage";; 1723 1724 *) _invalid_option_for "usage";; 1725 esac 1726 done 1727 1728 shift $((OPTIND-1)) 1729 [ "$1" = "--" ] && shift 1730 1731 # There was a bug with some shells, which were adding extra commands 1732 # to the old dynamic-loading version of this code. 1733 # thanks to @antmak it is now fixed, see: 1734 # https://github.com/sobolevn/git-secret/issues/47 1735 local commands="add|cat|changes|clean|hide|init|killperson|list|remove|reveal|tell|usage|whoknows" 1736 1737 echo "usage: git secret [--version] [$commands]" 1738 echo " 'git secret --version' will show version and exit" 1739 echo "See 'git secret [command] -h' for more info about commands and their options" 1740 echo " add [filename.txt] - adds file to be hidden, optionally adds file to .gitignore" 1741 echo " cat [filename.txt] - cats the decrypted contents of the named file to stdout" 1742 echo " changes [filename.secret] - indicates if the file has changed since checkin" 1743 echo " clean - deletes encrypted files" 1744 echo " hide - encrypts (or re-encrypts) the files to be hidden" 1745 echo " init - creates the .gitsecret directory and contents needed for git-secret" 1746 echo " killperson [emails] - the reverse of 'tell', removes access for the named user" 1747 echo " list - shows files to be hidden/encrypted, as in .gitsecret/paths/mapping.cfg" 1748 echo " remove [files] - removes files from list of hidden files" 1749 echo " reveal - decrypts all hidden files, as mentioned in 'git secret list'" 1750 echo " tell [email] - add access for the user with imported public key with email" 1751 echo " whoknows - shows list of email addresses associated with public keys that can reveal files" 1752} 1753#!/usr/bin/env bash 1754 1755 1756function whoknows { 1757 OPTIND=1 1758 1759 local long_display=0 1760 while getopts "hl?" opt; do 1761 case "$opt" in 1762 h) _show_manual_for "whoknows";; 1763 1764 l) long_display=1;; # like ls -l 1765 1766 *) _invalid_option_for "whoknows";; 1767 esac 1768 done 1769 1770 shift $((OPTIND-1)) 1771 [ "$1" = "--" ] && shift 1772 1773 # Validating, that we have a user: 1774 _user_required 1775 1776 local users 1777 1778 # Getting the users from gpg: 1779 users=$(_get_users_in_gitsecret_keyring) 1780 for user in $users; do 1781 echo -n "$user" 1782 1783 if [[ "$long_display" -eq 1 ]]; then 1784 local expiration 1785 expiration=$(_get_user_key_expiry "$user") 1786 if [[ -n "$expiration" ]]; then 1787 local expiration_date 1788 expiration_date=$($SECRETS_EPOCH_TO_DATE "$expiration") 1789 echo -n " (expires: $expiration_date)" 1790 else 1791 echo -n " (expires: never)" 1792 fi 1793 fi 1794 1795 echo 1796 done 1797} 1798#!/usr/bin/env bash 1799 1800set -e 1801 1802function _check_setup { 1803 # Checking git and secret-plugin setup: 1804 local is_tree 1805 is_tree=$(_is_inside_git_tree) 1806 if [[ "$is_tree" -ne 0 ]]; then 1807 _abort "not in dir with git repo. Use 'git init' or 'git clone', then in repo use 'git secret init'" 1808 fi 1809 1810 # Checking if the '.gitsecret' dir (or as set by SECRETS_DIR) is not ignored: 1811 _secrets_dir_is_not_ignored 1812 1813 # Checking gpg setup: 1814 local keys_dir 1815 keys_dir=$(_get_secrets_dir_keys) 1816 1817 local secring="$keys_dir/secring.gpg" 1818 if [[ -f $secring ]] && [[ -s $secring ]]; then 1819 # secring.gpg exists and is not empty, 1820 # someone has imported a private key. 1821 _abort 'it seems that someone has imported a secret key.' 1822 fi 1823} 1824 1825 1826function _incorrect_usage { 1827 echo "git-secret: abort: $1" 1828 usage 1829 exit "$2" 1830} 1831 1832 1833function _show_version { 1834 echo "$GITSECRET_VERSION" 1835 exit 0 1836} 1837 1838 1839function _init_script { 1840 if [[ $# == 0 ]]; then 1841 _incorrect_usage 'no input parameters provided.' 126 1842 fi 1843 1844 # Parse plugin-level options: 1845 local dry_run=0 1846 1847 while [[ $# -gt 0 ]]; do 1848 local opt="$1" 1849 1850 case "$opt" in 1851 # Options for quick-exit strategy: 1852 --dry-run) 1853 dry_run=1 1854 shift;; 1855 1856 --version) _show_version;; 1857 1858 *) break;; # do nothing 1859 esac 1860 done 1861 1862 if [[ "$dry_run" == 0 ]]; then 1863 # Checking for proper set-up: 1864 _check_setup 1865 1866 # Routing the input command: 1867 local function_exists 1868 function_exists=$(_function_exists "$1") 1869 1870 if [[ "$function_exists" == 0 ]] && [[ ! $1 == _* ]]; then 1871 $1 "${@:2}" 1872 else # TODO: elif [[ $(_plugin_exists $1) == 0 ]]; then 1873 _incorrect_usage "command $1 not found." 126 1874 fi 1875 fi 1876} 1877 1878 1879_init_script "$@" 1880