-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsekret
executable file
·279 lines (231 loc) · 8.9 KB
/
sekret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#!/bin/bash
SCRIPT_PATH="$(dirname -- "$(readlink -f -- "$0")")/$(basename -- "$0")"
prefix="$SCRIPT_PATH"
debug() {
if [ "$DEBUG" == "true" ]; then
LOG_PATH="$HOME/dev/debug.log"
if [ -n "$EAARDAL_DEBUG_LOG_PATH" ]; then
LOG_PATH="$EAARDAL_DEBUG_LOG_PATH"
fi
echo "$@" >>"$LOG_PATH"
fi
}
print_help() {
echo -e ""
echo -e "Usage:"
echo -e ""
echo -e "About the <secret> format:"
echo -e " - TL;DR: Use the format {section}.{field} or {field}, where {field} does not contain dots (.). <secret> cannot containn more than one dot (.) which is to delimit section name from field name"
echo -e " - Use forward slash (/) or underscores (_) to 'namespace' your fields instead of dots (.)"
echo -e " - 1Password docs: https://developer.1password.com/docs/cli/reference/management-commands/item#item-create-flags"
echo -e ""
echo -e "sekret ls|list - List available secrets"
echo -e "sekret get <secret> - Print the secret value in plain text"
echo -e "sekret add <secret> <value> - Store the secret with the given name and value"
echo -e "sekret init - Create an item in 1Password called .sekret that Sekret will use as the backend storage for secrets"
}
# Fail script immediately on any errors
set -e
if [ -z "$1" ]; then
echo "No command provided, here's the help command instead:"
print_help
exit 0
fi
if [ -z "$SEKRET_DIR" ]; then
echo "Environment variable SEKRET_DIR is not set. It should be set to the directory where the 'sekret' executable is stored."
print_help
exit 1
fi
if [ -n "$SEKRET_1PASS_ITEM" ]; then
SECRET_ITEM=$SEKRET_1PASS_ITEM
else
SECRET_ITEM=".sekret"
fi
# Location of jq to use. Recommend using bundled jq for versioning stability.
if [ -n "$SEKRET_JQ" ]; then
JQ=$SEKRET_JQ
else
JQ="$SEKRET_DIR/bin/jq"
fi
# Authenticate using 1Password's biometrics: https://developer.1password.com/docs/cli/about-biometric-unlock#set-the-biometric-unlock-environment-variable
# Biometric unlock must be enabled in the 1Password UI app first: Preferences -> Developer -> Biometric unlock for 1Password CLI
if [ -z "$OP_BIOMETRIC_UNLOCK_ENABLED" ]; then
OP_BIOMETRIC_UNLOCK_ENABLED=true
fi
# Name of the vault in 1Password to use when doing 1Password operations.
if [ -z "$SEKRET_1PASS_VAULT" ]; then
SEKRET_1PASS_VAULT="Private"
fi
if [ -z "$SEKRET_1PASS_ACCOUNT" ]; then
SEKRET_1PASS_ACCOUNT="my.1password.com"
fi
if [ "$DEBUG" == "true" ]; then
debug "$prefix: Environment:"
debug "$prefix: ------"
debug "$prefix: SEKRET_DIR: $SEKRET_DIR"
debug "$prefix: SECRET_ITEM: $SECRET_ITEM"
debug "$prefix: SEKRET_1PASS_ITEM: $SEKRET_1PASS_ITEM"
debug "$prefix: SEKRET_1PASS_VAULT: $SEKRET_1PASS_VAULT"
debug "$prefix: SEKRET_1PASS_ACCOUNT: $SEKRET_1PASS_ACCOUNT"
debug "$prefix: SEKRET_JQ: $SEKRET_JQ"
debug "$prefix: JQ: $JQ"
debug "$prefix: OP_BIOMETRIC_UNLOCK_ENABLED: $OP_BIOMETRIC_UNLOCK_ENABLED"
debug "$prefix: Args: $*"
debug "$prefix: Arg 0: $0"
debug "$prefix: Arg 1: $1"
debug "$prefix: Arg 2: $2"
debug "$prefix: Arg 3: $3"
debug "$prefix: Arg 4: $4"
debug "$prefix: ------"
debug "$prefix: "
fi
item_exists() {
local result
# Query 1P to see if this item exists. Also redirect stderr to prevent the default error when an item does not exist.
result=$(op item get "$SECRET_ITEM" --vault "$SEKRET_1PASS_VAULT" --account "$SEKRET_1PASS_ACCOUNT" 2>&1)
if [[ "$result" == *"[ERROR]"* ]] && [[ "$result" == *"isn't an item"* ]]; then
echo "false"
else
echo "true"
fi
}
field_exists() {
if [ -z "$1" ]; then echo "No <field> provided" && print_help && exit 1; fi
local fieldPath=$1
local result
# Query 1P to see if this field exists in the given item. Also redirect stderr to prevent the default error from writing to the terminal when an item does not exist.
result=$(op item get "$SECRET_ITEM" --fields label="$fieldPath" --vault "$SEKRET_1PASS_VAULT" --account "$SEKRET_1PASS_ACCOUNT" 2>&1)
# If the field does not exist in the item, it will throw an error like "[ERROR] 2023/06/16 14:25:34 "my.secret/to/something" isn't a field in the ".sekret" item".
# This checks for partial matches against that error, which indicates wether the field exists or not.
if [[ "$result" == *"[ERROR]"* ]] && [[ "$result" == *"isn't a field"* ]]; then
echo "false"
else
echo "true"
fi
}
init() {
local exists
exists=$(item_exists 2>&1)
if [[ "$exists" == "false" ]]; then
echo "Could not find the item $SECRET_ITEM in 1Password, creating it"
op item create \
--category "Secure Note" \
--title "$SECRET_ITEM" \
--vault "$SEKRET_1PASS_VAULT" \
--account "$SEKRET_1PASS_ACCOUNT" \
--tags dev,sekret,secret,secrets
echo "Created $SECRET_ITEM"
else
echo "An item named $SECRET_ITEM already exists in 1Password"
fi
}
list() {
local itemJson
local fields
itemJson=$(op item get "$SECRET_ITEM" --format json --vault "$SEKRET_1PASS_VAULT" --account "$SEKRET_1PASS_ACCOUNT")
# 1. .fields[] - Select the items in the array named fields.
# 2. select(.label != "notesPlain") - Don't include the built-in field notesPlain which is for plain text notes (think of "select" as "where" or "filter" in other languages).
# 3. map the .label and .section.label fields into a new json object with 'field' and 'section' properties.
fields=$(echo "$itemJson" | $JQ -r '.fields[] | select(.label != "notesPlain") | { field: .label, section: .section.label, ref: .reference }')
echo "$fields" | $JQ -c '.' | while read -r i; do
field=$(echo "$i" | $JQ -r '.field')
section=$(echo "$i" | $JQ -r '.section')
ref=$(echo "$i" | $JQ -r '.ref')
if [ -z "$section" ] || [ "$section" = "null" ]; then
echo "$field -- $ref"
else
echo "$section.$field -- $ref"
fi
done
}
add_secret_value() {
if [ -z "$1" ]; then echo "No <field> provided" && print_help && exit 1; fi
local fieldPath=$1
if [ -z "$2" ]; then echo "No <value> provided" && print_help && exit 1; fi
local fieldValue=$2
if [ -n "$3" ]; then
local force="$3"
fi
local exists
local result
exists=$(field_exists "$fieldPath")
if [ "$exists" == "true" ] && [ "$force" != "true" ]; then
echo "Field $fieldPath already exists. Use another name or supply the --force flag to overwrite the existing value"
return
elif [ "$exists" == "true" ] && [ "$force" == "true" ]; then
# Edit the $SECRET_ITEM item by appending another field and field value
op item edit "$SECRET_ITEM" "$fieldPath=$fieldValue" --vault "$SEKRET_1PASS_VAULT" --account "$SEKRET_1PASS_ACCOUNT"
echo "$fieldPath was edited"
elif [ "$exists" == "false" ]; then
# Use the 'edit' command since we're only appending new labels to the same $SECRET_ITEM item. The 'create' command would create an entirely new item instead.
op item edit "$SECRET_ITEM" "$fieldPath=$fieldValue" --vault "$SEKRET_1PASS_VAULT" --account "$SEKRET_1PASS_ACCOUNT"
echo "$fieldPath was created"
fi
}
add() {
if [ -z "$2" ]; then echo "No <field> provided" && print_help && exit 1; fi
local fieldPath=$2
if [ -z "$3" ]; then echo "No <value> provided" && print_help && exit 1; fi
local fieldValue=$3
if [ "$4" == "--force" ]; then
local force="true"
fi
# If $fieldValue is a file on disk, read the file's content and use as value
if [ -f "$fieldValue" ]; then
local filePath="$fieldValue"
local fileContent=$(cat "$filePath" | base64)
local fileName=$(basename "$filePath")
# Ensure files are prefixed with "file:" so we can check if we're reading a file when querying the secret later.
fieldValue="file:$fileName:$fileContent"
fi
# If fieldPath contains "." we assume the part before the dot is the section name and the part after is the field name in that section
if [[ $fieldPath == *"."* ]]; then
local section
local field
section=$(cut -d'.' -f1 <<<"$fieldPath") # Split by dot and take first part as section
field=$(cut -d'.' -f2 <<<"$fieldPath") # Split by dot and take second part as field
add_secret_value "$section.$field" "$fieldValue" "$force"
else
add_secret_value "$fieldPath" "$fieldValue" "$force"
fi
}
get() {
if [ -z "$2" ]; then echo "No <field> provided" && print_help && exit 1; fi
local field=$2
value=$(op item get "$SECRET_ITEM" --fields label="$field" --vault "$SEKRET_1PASS_VAULT" --account "$SEKRET_1PASS_ACCOUNT")
# Check if the value is a file by checking if the content starts with "file:".
if [[ "$value" = file:* ]]; then
# Get the different parts and base64 decode the file's contents if it is a file.
fileName=$(cut -d':' -f2 <<<"$value")
fileContent=$(cut -d':' -f3 <<<"$value")
echo "$fileContent" | base64 --decode
else
# Just print the value if it's not a file.
echo "$value"
fi
}
case $1 in
"help")
print_help
exit 0
;;
"init")
init "$@"
;;
"ls" | "list")
list "$@"
;;
"add")
add "$@"
;;
"get")
get "$@"
;;
*)
echo "Unknown command '$1'"
print_help
exit 1
;;
esac
# Restore bash error flag
set +e