-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ef4c123
Showing
4 changed files
with
322 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Alexandre Lotte | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Keychain Secrets manager | ||
|
||
Straight-forward command line secrets manager powered by the Keychain tools already available on macOS systems. | ||
|
||
It's a tiny and easy to use CLI that let's you securely store and retrieve encrypted secrets from a Keychain without any additional third parties involved. | ||
|
||
It's built as a small wrapper around native `security` command, so it's fast, works offline and is fully interoperable with the Keychain Access app. This way you can also manage your secrets via the UI as well. | ||
|
||
**This is for you if**: | ||
|
||
- You're on macOS. | ||
- You want to store and retrieve secrets via the command-line using simple commands. | ||
- You like leveraging existing OS functionnality. | ||
- You don't like the idea of relying on an HTTP connection, a third party service and a credit card subscription to manage secrets. | ||
|
||
## Installation | ||
|
||
Use the install script for an simple interactive installation, that will, in short: | ||
|
||
1. Download the script file from github. | ||
2. Place it into the chosen executable path and make sure it's executable. | ||
3. Create a Keychain to store secrets in and register it in Keychain Access app. | ||
|
||
```sh | ||
curl -sSLf https://raw.githubusercontent.com/loteoo/ks/main/install | bash | ||
``` | ||
|
||
## Manual installation | ||
|
||
Clone this repo somewhere on your machine, then create a symlink in your executable bin folder to the script. | ||
|
||
```sh | ||
# This directory should be in your executable PATH | ||
# / | ||
ln -s ~/path/to/repo/ks ~/bin/ks | ||
# \ | ||
# This should point to the actual secret file | ||
``` | ||
|
||
To setup completions run something like this: | ||
|
||
```sh | ||
echo "source <(ks completion)" >> ~/.zshrc | ||
``` | ||
|
||
## Usage | ||
|
||
``` | ||
$ ks help | ||
Keychain Secrets manager | ||
Usage: | ||
ks add <key> <value> Add an encrypted secret | ||
ks show <key> Decrypt and reveal a secret | ||
ks ls List secret keys | ||
ks rm <key> Remove a secret | ||
ks help | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
#!/usr/bin/env bash | ||
set -euo pipefail | ||
IFS=$'\n\t' | ||
dimmed=$'\e[2m' | ||
bold=$(tput bold) | ||
cyan=$'\e[96m' | ||
normal=$(tput sgr0) | ||
|
||
SCRIPT_NAME="ks" | ||
SCRIPT_DOWNLOAD_URL="https://raw.githubusercontent.com/loteoo/ks/main/ks" | ||
REQUIRES_RESTART="false" | ||
SYSTEM_BIN_PATH="/usr/local/bin" | ||
LOCAL_BIN_PATH="$HOME/.local/bin" | ||
KEYCHAIN="${KS_DEFAULT_KEYCHAIN:-Secrets}" | ||
KEYCHAIN_FILE="$KEYCHAIN.keychain" | ||
|
||
info() { | ||
# shellcheck disable=SC2145 | ||
echo "${dimmed}$@${normal}" 1>&2 | ||
} | ||
|
||
success() { | ||
# shellcheck disable=SC2145 | ||
echo "${cyan}$@${normal}" | ||
} | ||
|
||
throw() { | ||
echo "$@" 1>&2 | ||
exit 1 | ||
} | ||
|
||
yn() { | ||
read -r -n 1 -p "$1 [y/n]: " yn | ||
echo | ||
if [[ "$yn" != [Yy]* ]]; then | ||
return 1 | ||
fi | ||
} | ||
|
||
echo "Select install location: | ||
1) For $USER user only. Script will be placed under: $LOCAL_BIN_PATH. | ||
2) For whole system. Script will be placed under: $SYSTEM_BIN_PATH. Requires sudo." | ||
read -r -n 1 -p "Pick one: " num | ||
echo | ||
case "$num" in | ||
1) BIN_PATH="$LOCAL_BIN_PATH";; | ||
2) BIN_PATH="$SYSTEM_BIN_PATH";; | ||
*) throw "Invalid choice.";; | ||
esac | ||
|
||
# Create bin directory if needed. | ||
if [[ ! -d "$BIN_PATH" ]]; then | ||
if ! yn "Need to create $BIN_PATH. Continue?"; then | ||
throw "Installation aborted." | ||
fi | ||
mkdir "$BIN_PATH" | ||
success "==> Created $BIN_PATH folder." | ||
else | ||
info "✓ Directory exists." | ||
fi | ||
|
||
# Add to bin directory to $PATH if needed. | ||
if ! echo "$PATH" | tr ':' $'\n' | grep -q "$BIN_PATH"; then | ||
if ! yn "$BIN_PATH has not been added to \$PATH. Add to path? (requires sudo privileges)"; then | ||
throw "Installation aborted." | ||
fi | ||
sudo bash -c "echo '$BIN_PATH' >> /etc/paths" | ||
REQUIRES_RESTART="true" | ||
success "==> $BIN_PATH was added to \$PATH." | ||
else | ||
info "✓ Directory is in \$PATH." | ||
fi | ||
|
||
SCRIPT_PATH="$BIN_PATH/$SCRIPT_NAME" | ||
|
||
if [[ -f "$SCRIPT_PATH" ]]; then | ||
if ! yn "File $SCRIPT_PATH already exists. Replace?"; then | ||
throw "Installation aborted." | ||
fi | ||
fi | ||
|
||
info "Downloading $SCRIPT_DOWNLOAD_URL..." | ||
curl -sSLf "$SCRIPT_DOWNLOAD_URL" -o "$SCRIPT_PATH" | ||
success "==> Script added under $SCRIPT_PATH" | ||
|
||
if [[ ! -x "$SCRIPT_PATH" ]]; then | ||
chmod +x "$SCRIPT_PATH" | ||
success "==> Script made executable." | ||
else | ||
info "✓ Script is executable." | ||
fi | ||
|
||
if ! security show-keychain-info "$KEYCHAIN_FILE" > /dev/null 2>&1; then | ||
$SCRIPT_NAME init | ||
else | ||
info "✓ Keychain \"$KEYCHAIN\" exists." | ||
fi | ||
|
||
if [[ "$REQUIRES_RESTART" == "true" ]]; then | ||
info "⚠️ Restart your terminal to make $SCRIPT_NAME available." | ||
fi | ||
|
||
info "Give $SCRIPT_NAME it a star on Github if you like it! 🙏" | ||
success "${bold}Installation completed! 🎉${normal}" | ||
eval "$SCRIPT_NAME help" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
#!/usr/bin/env bash | ||
|
||
add() { | ||
key="${1:?'Please provide the name of the secret to add.'}" | ||
value="${2:?'Please provide the value to encrypt.'}" | ||
security add-generic-password \ | ||
-a "$USER" \ | ||
-D secret \ | ||
-s "$key" \ | ||
-w "$value" \ | ||
"$KEYCHAIN_FILE" \ | ||
2> /dev/null \ | ||
|| throw "Secret \"$key\" already exists." | ||
success "Secret \"$key\" added." | ||
} | ||
|
||
show() { | ||
key="${1:?'Please provide the name of the secret to show.'}" | ||
security find-generic-password \ | ||
-a "$USER" \ | ||
-s "$key" \ | ||
-w \ | ||
"$KEYCHAIN_FILE" \ | ||
2> /dev/null \ | ||
|| throw "Secret \"$key\" was not found in keychain." | ||
} | ||
|
||
rm() { | ||
key="${1:?'Please provide the name of the secret to remove.'}" | ||
security delete-generic-password \ | ||
-a "$USER" \ | ||
-s "$key" \ | ||
"$KEYCHAIN_FILE" \ | ||
> /dev/null 2>&1 \ | ||
|| throw "Secret \"$key\" was not found in keychain." | ||
success "Secret \"$key\" deleted." | ||
} | ||
|
||
ls() { | ||
security dump-keychain "$KEYCHAIN_FILE" \ | ||
| grep 0x00000007 \ | ||
| awk -F= '{print $2}' \ | ||
| tr -d \" \ | ||
|| throw "Keychain is empty." | ||
} | ||
|
||
init() { | ||
maybe_error="$(cat <(security show-keychain-info "$KEYCHAIN_FILE" 2>&1))" | ||
not_found_error="The specified keychain could not be found" | ||
if [[ "$maybe_error" == *"SecKeychainCopySettings"* ]]; then | ||
if [[ "$maybe_error" == *"$not_found_error"* ]]; then | ||
if ! yn "Keychain \"$KEYCHAIN\" does not exist. Create it?"; then | ||
throw "Aborted." | ||
fi | ||
if [[ "$KEYCHAIN" =~ [^a-zA-Z0-9_-] ]]; then | ||
throw "Invalid keychain name. Alphanumeric with dashes and underscores only." | ||
fi | ||
echo "Choose a password for the keychain. You can change it later via the Keychain Access app." | ||
read -rsp "Password: " pass | ||
echo | ||
security create-keychain -p "$pass" "$KEYCHAIN_FILE" | ||
success "Keychain \"$KEYCHAIN\" created." | ||
if yn "Register in Keychain Access app?"; then | ||
eval "security list-keychains -s $(security default-keychain) $(security list-keychains | xargs) $HOME/Library/Keychains/$KEYCHAIN_FILE" | ||
success "Keychain \"$KEYCHAIN\" added to Keychain Access app." | ||
fi | ||
else | ||
throw "Could not access the \"$KEYCHAIN\" keychain." | ||
fi | ||
fi | ||
} | ||
|
||
# Meta stuff... | ||
# =================== | ||
set -euo pipefail | ||
IFS=$'\n\t' | ||
normal=$(tput sgr0) | ||
cyan=$'\e[96m' | ||
|
||
KEYCHAIN="${KS_DEFAULT_KEYCHAIN:-Secrets}" | ||
|
||
help() { | ||
cat << EOT | ||
Keychain Secrets manager | ||
Usage: | ||
ks [-k keychain] <action> [...opts] | ||
Commands: | ||
add <key> <value> Add an encrypted secret | ||
show <key> Decrypt and reveal a secret | ||
rm <key> Remove a secret | ||
ls List secret keys | ||
init Create the specified Keychain | ||
help Show this help text | ||
EOT | ||
} | ||
|
||
completion() { | ||
echo "complete -W \"add show rm ls help\" ks" | ||
} | ||
|
||
success() { | ||
# shellcheck disable=SC2145 | ||
echo "${cyan}✓ $@${normal}" | ||
} | ||
|
||
throw() { | ||
echo "$@" 1>&2 | ||
exit 1 | ||
} | ||
|
||
yn() { | ||
read -r -n 1 -p "$1 [y/n]: " yn | ||
echo | ||
if [[ "$yn" != [Yy]* ]]; then | ||
return 1 | ||
fi | ||
} | ||
|
||
while getopts "k:" arg; do | ||
case $arg in | ||
k) KEYCHAIN="$OPTARG";; | ||
*) throw "$(help)";; | ||
esac | ||
done | ||
shift $((OPTIND - 1)) | ||
|
||
KEYCHAIN_FILE="$KEYCHAIN.keychain" | ||
|
||
if [[ "$(type -t "${1:-}")" == "function" ]]; then | ||
if [[ "add show rm ls" = *"$1"* ]]; then | ||
init | ||
fi | ||
$1 "${@:2}" | ||
else | ||
throw "$(help)" | ||
fi |