backend: fix use of SSH private key for git ops

Primarily this is supposed to fix the `git pull` aciton triggered by
webhooks from GitHub. But in addition to that the point is to simplify
that committing wrapper which has far too much in it.

Instead of passing everything as CLI arguments one should make use of
already supported environment variables and the `env` argument to
`subprocess` functions like `run()`. Writing extra logic in the wrapper
only makes it unnecessarily complicated.

By passing both user, email, and the SSH options in `run_shell_command`
we avoid the need to repeat the same boilerplate to provide Git config
and SSH credentials.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2023-02-20 22:21:31 +01:00
parent 2d76a39365
commit 1e8e6a7968
3 changed files with 24 additions and 67 deletions

View File

@ -12,17 +12,9 @@ set -o errtrace -o errexit -o nounset -o pipefail
bpmn_models_absolute_dir="$1" bpmn_models_absolute_dir="$1"
git_commit_message="$2" git_commit_message="$2"
git_branch="$3" git_branch="$3"
git_commit_username="$4"
git_commit_email="$5"
git_commit_password="$6"
if [[ -z "${5:-}" ]]; then if [[ -z "${3:-}" ]]; then
>&2 echo "usage: $(basename "$0") [bpmn_models_absolute_dir] [git_commit_message] [git_branch] [git_commit_username] [git_commit_email]" >&2 echo "usage: $(basename "${0}") [bpmn_models_absolute_dir] [git_commit_message] [git_branch]"
exit 1
fi
if [[ -z "$git_commit_password" && -z "${SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY:-}" ]]; then
>&2 echo "ERROR: A git password or SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY must be provided"
exit 1 exit 1
fi fi
@ -32,38 +24,20 @@ function failed_to_get_lock() {
} }
function run() { function run() {
cd "$bpmn_models_absolute_dir" cd "${bpmn_models_absolute_dir}"
git add . git add .
# https://unix.stackexchange.com/a/155077/456630 # https://unix.stackexchange.com/a/155077/456630
if [ -z "$(git status --porcelain)" ]; then if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit" echo "No changes to commit"
else return
git config --local user.name "$git_commit_username"
git config --local user.email "$git_commit_email"
if [[ -n "${SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY:-}" ]]; then
tmpfile=$(mktemp /tmp/tmp_git.XXXXXX)
chmod 600 "$tmpfile"
echo "$SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY" >"$tmpfile"
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i ${tmpfile} -F /dev/null"
else
PAT="${git_commit_username}:${git_commit_password}"
AUTH=$(echo -n "$PAT" | openssl base64 | tr -d '\n')
git config --local http.extraHeader "Authorization: Basic $AUTH"
fi
git commit -m "$git_commit_message"
git push --set-upstream origin "$git_branch"
if [[ -z "${SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY:-}" ]]; then
git config --unset --local http.extraHeader
fi
fi fi
git commit -m "${git_commit_message}"
git push --set-upstream origin "${git_branch}"
} }
exec {lock_fd}>/var/lock/mylockfile || failed_to_get_lock exec {lock_fd}>/var/lock/mylockfile || failed_to_get_lock
flock --timeout 60 "$lock_fd" || failed_to_get_lock flock --timeout 60 "${lock_fd}" || failed_to_get_lock
run run
flock -u "$lock_fd" flock -u "${lock_fd}"

View File

@ -96,9 +96,6 @@ SPIFFWORKFLOW_BACKEND_GIT_PUBLISH_CLONE_URL = environ.get(
SPIFFWORKFLOW_BACKEND_GIT_COMMIT_ON_SAVE = ( SPIFFWORKFLOW_BACKEND_GIT_COMMIT_ON_SAVE = (
environ.get("SPIFFWORKFLOW_BACKEND_GIT_COMMIT_ON_SAVE", default="false") == "true" environ.get("SPIFFWORKFLOW_BACKEND_GIT_COMMIT_ON_SAVE", default="false") == "true"
) )
SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY = environ.get(
"SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY"
)
SPIFFWORKFLOW_BACKEND_GIT_USERNAME = environ.get("SPIFFWORKFLOW_BACKEND_GIT_USERNAME") SPIFFWORKFLOW_BACKEND_GIT_USERNAME = environ.get("SPIFFWORKFLOW_BACKEND_GIT_USERNAME")
SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL = environ.get( SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL = environ.get(
"SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL" "SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL"
@ -106,11 +103,8 @@ SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL = environ.get(
SPIFFWORKFLOW_BACKEND_GITHUB_WEBHOOK_SECRET = environ.get( SPIFFWORKFLOW_BACKEND_GITHUB_WEBHOOK_SECRET = environ.get(
"SPIFFWORKFLOW_BACKEND_GITHUB_WEBHOOK_SECRET", default=None "SPIFFWORKFLOW_BACKEND_GITHUB_WEBHOOK_SECRET", default=None
) )
SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY = environ.get( SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY_PATH = environ.get(
"SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY", default=None "SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY_PATH", default=None
)
SPIFFWORKFLOW_BACKEND_GIT_USER_PASSWORD = environ.get(
"SPIFFWORKFLOW_BACKEND_GIT_USER_PASSWORD", default=None
) )
# Database Configuration # Database Configuration

View File

@ -94,19 +94,7 @@ class GitService:
raise ConfigurationError( raise ConfigurationError(
"SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR config must be set" "SPIFFWORKFLOW_BACKEND_BPMN_SPEC_ABSOLUTE_DIR config must be set"
) )
if current_app.config["SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY"]:
os.environ["SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY"] = (
current_app.config["SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY"]
)
git_username = ""
git_email = ""
if (
current_app.config["SPIFFWORKFLOW_BACKEND_GIT_USERNAME"]
and current_app.config["SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL"]
):
git_username = current_app.config["SPIFFWORKFLOW_BACKEND_GIT_USERNAME"]
git_email = current_app.config["SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL"]
shell_command_path = os.path.join( shell_command_path = os.path.join(
current_app.root_path, "..", "..", "bin", "git_commit_bpmn_models_repo" current_app.root_path, "..", "..", "bin", "git_commit_bpmn_models_repo"
) )
@ -115,9 +103,6 @@ class GitService:
repo_path_to_use, repo_path_to_use,
message, message,
branch_name_to_use, branch_name_to_use,
git_username,
git_email,
current_app.config["SPIFFWORKFLOW_BACKEND_GIT_USER_PASSWORD"],
] ]
return cls.run_shell_command_to_get_stdout(shell_command) return cls.run_shell_command_to_get_stdout(shell_command)
@ -169,8 +154,17 @@ class GitService:
cls, command: list[str], return_success_state: bool = False cls, command: list[str], return_success_state: bool = False
) -> Union[subprocess.CompletedProcess[bytes], bool]: ) -> Union[subprocess.CompletedProcess[bytes], bool]:
"""Run_shell_command.""" """Run_shell_command."""
env = {
'GIT_COMMITTER_NAME': current_app.config.get("SPIFFWORKFLOW_BACKEND_GIT_USERNAME", "unknown"),
'GIT_COMMITTER_EMAIL': current_app.config.get("SPIFFWORKFLOW_BACKEND_GIT_USER_EMAIL", "unknown@example.org"),
}
# SSH authentication can be also provided via gitconfig.
ssh_key_path = current_app.config.get("SPIFFWORKFLOW_BACKEND_GIT_SSH_PRIVATE_KEY_PATH")
if ssh_key_path is not None:
env['GIT_SSH_COMMAND'] = 'ssh -F /dev/null -i %s' % ssh_key_path
# this is fine since we pass the commands directly # this is fine since we pass the commands directly
result = subprocess.run(command, check=False, capture_output=True) # noqa result = subprocess.run(command, check=False, capture_output=True, env=env) # noqa
if return_success_state: if return_success_state:
return result.returncode == 0 return result.returncode == 0
@ -178,9 +172,9 @@ class GitService:
stdout = result.stdout.decode("utf-8") stdout = result.stdout.decode("utf-8")
stderr = result.stderr.decode("utf-8") stderr = result.stderr.decode("utf-8")
raise GitCommandError( raise GitCommandError(
f"Failed to execute git command: {command} " f"Failed to execute git command: {command}"
f"Stdout: {stdout} " f"Stdout: {stdout}"
f"Stderr: {stderr} " f"Stderr: {stderr}"
) )
return result return result
@ -252,11 +246,6 @@ class GitService:
git_clone_url = current_app.config[ git_clone_url = current_app.config[
"SPIFFWORKFLOW_BACKEND_GIT_PUBLISH_CLONE_URL" "SPIFFWORKFLOW_BACKEND_GIT_PUBLISH_CLONE_URL"
] ]
if git_clone_url.startswith("https://"):
git_clone_url = git_clone_url.replace(
"https://",
f"https://{current_app.config['SPIFFWORKFLOW_BACKEND_GIT_USERNAME']}:{current_app.config['SPIFFWORKFLOW_BACKEND_GIT_USER_PASSWORD']}@",
)
cmd = ["git", "clone", git_clone_url, destination_process_root] cmd = ["git", "clone", git_clone_url, destination_process_root]
cls.run_shell_command(cmd) cls.run_shell_command(cmd)