granny-devops 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- granny/__init__.py +19 -0
- granny/analyze/__init__.py +6 -0
- granny/analyze/lambdas.py +59 -0
- granny/analyze/vpcs.py +57 -0
- granny/cdn/__init__.py +9 -0
- granny/cdn/bunny.py +231 -0
- granny/cli/__init__.py +0 -0
- granny/cli/analyze.py +66 -0
- granny/cli/cdn.py +210 -0
- granny/cli/create.py +94 -0
- granny/cli/credentials.py +99 -0
- granny/cli/dns.py +290 -0
- granny/cli/docker.py +165 -0
- granny/cli/edge.py +106 -0
- granny/cli/email.py +224 -0
- granny/cli/main.py +98 -0
- granny/cli/serverless.py +278 -0
- granny/cli/storage.py +249 -0
- granny/create/__init__.py +4 -0
- granny/create/auto_certificate.py +1899 -0
- granny/create/cloudfront-security-headers.js +53 -0
- granny/create/manage-dns.sh +321 -0
- granny/create/manage_mailjet_contacts.py +619 -0
- granny/create/registrars.py +363 -0
- granny/create/setup_aws_cloudfront.py +2808 -0
- granny/create/setup_bunny_edge_script.py +923 -0
- granny/create/setup_bunny_storage.py +1719 -0
- granny/create/setup_cognito_identity_pool.py +740 -0
- granny/create/setup_hetzner_bunny.py +1482 -0
- granny/create/setup_mailjet_dns.py +1103 -0
- granny/create/setup_private_cdn.py +547 -0
- granny/create/setup_s3_website.py +1512 -0
- granny/create/setup_scaleway_faas.py +1165 -0
- granny/create/setup_workmail.py +1217 -0
- granny/create/www-redirect-function.js +17 -0
- granny/credentials/__init__.py +15 -0
- granny/credentials/secrets.py +403 -0
- granny/dns/__init__.py +22 -0
- granny/dns/base.py +113 -0
- granny/dns/bunny.py +150 -0
- granny/dns/cloudflare.py +192 -0
- granny/dns/cloudns.py +162 -0
- granny/dns/desec.py +152 -0
- granny/dns/factory.py +72 -0
- granny/dns/hetzner.py +165 -0
- granny/dns/manual.py +64 -0
- granny/dns/records.py +29 -0
- granny/docker/__init__.py +5 -0
- granny/docker/build_base.py +204 -0
- granny/edge/__init__.py +5 -0
- granny/edge/bunny.py +147 -0
- granny/email/__init__.py +7 -0
- granny/email/mailjet.py +119 -0
- granny/email/mailjet_contacts.py +115 -0
- granny/email/ses_forwarding.py +281 -0
- granny/email/workmail.py +145 -0
- granny/report.py +128 -0
- granny/serverless/__init__.py +5 -0
- granny/serverless/scaleway.py +264 -0
- granny/storage/__init__.py +7 -0
- granny/storage/aws.py +113 -0
- granny/storage/bunny.py +98 -0
- granny/storage/hetzner.py +118 -0
- granny_devops-0.4.0.dist-info/METADATA +445 -0
- granny_devops-0.4.0.dist-info/RECORD +68 -0
- granny_devops-0.4.0.dist-info/WHEEL +4 -0
- granny_devops-0.4.0.dist-info/entry_points.txt +2 -0
- granny_devops-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
function handler(event) {
|
|
2
|
+
var response = event.response;
|
|
3
|
+
var headers = response.headers;
|
|
4
|
+
|
|
5
|
+
// Strict Transport Security - Force HTTPS for 1 year
|
|
6
|
+
headers['strict-transport-security'] = {
|
|
7
|
+
value: 'max-age=31536000; includeSubDomains; preload'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Content Security Policy - Allow your domains and required resources
|
|
11
|
+
headers['content-security-policy'] = {
|
|
12
|
+
value: "default-src 'self' https:; " +
|
|
13
|
+
"script-src 'self' 'unsafe-eval' 'unsafe-inline' https: blob:; " +
|
|
14
|
+
"style-src 'self' 'unsafe-inline' https:; " +
|
|
15
|
+
"img-src 'self' https: data: blob:; " +
|
|
16
|
+
"font-src 'self' https: data:; " +
|
|
17
|
+
"connect-src 'self' https: wss:; " +
|
|
18
|
+
"media-src 'self' https: blob:; " +
|
|
19
|
+
"object-src 'none'; " +
|
|
20
|
+
"base-uri 'self'; " +
|
|
21
|
+
"form-action 'self' https:; " +
|
|
22
|
+
"frame-ancestors 'none'; " +
|
|
23
|
+
"worker-src 'self' blob:; " +
|
|
24
|
+
"manifest-src 'self';"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Prevent MIME type sniffing
|
|
28
|
+
headers['x-content-type-options'] = {
|
|
29
|
+
value: 'nosniff'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Prevent clickjacking
|
|
33
|
+
headers['x-frame-options'] = {
|
|
34
|
+
value: 'DENY'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Control referrer information
|
|
38
|
+
headers['referrer-policy'] = {
|
|
39
|
+
value: 'strict-origin-when-cross-origin'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Permissions Policy - Disable sensitive APIs by default
|
|
43
|
+
headers['permissions-policy'] = {
|
|
44
|
+
value: 'geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=()'
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// X-XSS-Protection (legacy but still useful)
|
|
48
|
+
headers['x-xss-protection'] = {
|
|
49
|
+
value: '1; mode=block'
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Cloudflare DNS Record Manager
|
|
4
|
+
# Manages DNS A records for pseekoo.io and pseekoo.com subdomains
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ./manage-dns.sh add <subdomain> # Add/update A record with auto-detected IP (pseekoo.io)
|
|
8
|
+
# ./manage-dns.sh add <subdomain> <ip> # Add/update A record with specific IP
|
|
9
|
+
# ./manage-dns.sh add <subdomain> --domain <domain> # Use specific domain
|
|
10
|
+
# ./manage-dns.sh delete <subdomain> # Delete A record
|
|
11
|
+
# ./manage-dns.sh list [--domain <domain>] # List all DNS records
|
|
12
|
+
# ./manage-dns.sh get <subdomain> # Get specific record details
|
|
13
|
+
#
|
|
14
|
+
# Environment:
|
|
15
|
+
# DOMAIN=pseekoo.com ./manage-dns.sh add foo # Alternative way to set domain
|
|
16
|
+
#
|
|
17
|
+
# Examples:
|
|
18
|
+
# ./manage-dns.sh add qdrant # Creates qdrant.pseekoo.io
|
|
19
|
+
# ./manage-dns.sh add api 1.2.3.4 # Creates api.pseekoo.io with specific IP
|
|
20
|
+
# ./manage-dns.sh add holylist --domain pseekoo.com # Creates holylist.pseekoo.com
|
|
21
|
+
# ./manage-dns.sh delete qdrant # Deletes qdrant.pseekoo.io
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
set -e
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
ENV_FILE="$SCRIPT_DIR/.env"
|
|
28
|
+
DOMAIN="${DOMAIN:-pseekoo.io}" # Default domain, can be overridden via env or --domain flag
|
|
29
|
+
|
|
30
|
+
# Colors
|
|
31
|
+
RED='\033[0;31m'
|
|
32
|
+
GREEN='\033[0;32m'
|
|
33
|
+
BLUE='\033[0;34m'
|
|
34
|
+
YELLOW='\033[0;33m'
|
|
35
|
+
NC='\033[0m'
|
|
36
|
+
|
|
37
|
+
# Load environment
|
|
38
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
39
|
+
source "$ENV_FILE"
|
|
40
|
+
else
|
|
41
|
+
echo -e "${RED}Error: .env file not found at $ENV_FILE${NC}"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [[ -z "$CLOUDFLARE_API_TOKEN" ]]; then
|
|
46
|
+
echo -e "${RED}Error: CLOUDFLARE_API_TOKEN not set in .env${NC}"
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
CF_API="https://api.cloudflare.com/client/v4"
|
|
51
|
+
|
|
52
|
+
# Helper function for API calls
|
|
53
|
+
cf_api() {
|
|
54
|
+
local method="$1"
|
|
55
|
+
local endpoint="$2"
|
|
56
|
+
local data="$3"
|
|
57
|
+
|
|
58
|
+
if [[ -n "$data" ]]; then
|
|
59
|
+
curl -s -X "$method" "$CF_API$endpoint" \
|
|
60
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
|
61
|
+
-H "Content-Type: application/json" \
|
|
62
|
+
--data "$data"
|
|
63
|
+
else
|
|
64
|
+
curl -s -X "$method" "$CF_API$endpoint" \
|
|
65
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
|
66
|
+
-H "Content-Type: application/json"
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Get zone ID for domain
|
|
71
|
+
get_zone_id() {
|
|
72
|
+
local response
|
|
73
|
+
response=$(cf_api GET "/zones?name=$DOMAIN")
|
|
74
|
+
|
|
75
|
+
if ! echo "$response" | jq -e '.success' > /dev/null 2>&1; then
|
|
76
|
+
echo -e "${RED}Error: Failed to query Cloudflare API${NC}"
|
|
77
|
+
echo "$response" | jq . 2>/dev/null || echo "$response"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
local zone_id
|
|
82
|
+
zone_id=$(echo "$response" | jq -r '.result[0].id')
|
|
83
|
+
|
|
84
|
+
if [[ "$zone_id" == "null" || -z "$zone_id" ]]; then
|
|
85
|
+
echo -e "${RED}Error: Zone '$DOMAIN' not found${NC}"
|
|
86
|
+
exit 1
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
echo "$zone_id"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Get public IP of this server
|
|
93
|
+
get_public_ip() {
|
|
94
|
+
local ip
|
|
95
|
+
ip=$(curl -s https://api.ipify.org 2>/dev/null || \
|
|
96
|
+
curl -s https://ifconfig.me 2>/dev/null || \
|
|
97
|
+
curl -s https://icanhazip.com 2>/dev/null)
|
|
98
|
+
|
|
99
|
+
if [[ -z "$ip" ]]; then
|
|
100
|
+
echo -e "${RED}Error: Could not detect public IP${NC}"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
echo "$ip"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Get record ID by name
|
|
108
|
+
get_record_id() {
|
|
109
|
+
local zone_id="$1"
|
|
110
|
+
local name="$2"
|
|
111
|
+
local full_name="${name}.${DOMAIN}"
|
|
112
|
+
|
|
113
|
+
local response
|
|
114
|
+
response=$(cf_api GET "/zones/$zone_id/dns_records?type=A&name=$full_name")
|
|
115
|
+
|
|
116
|
+
echo "$response" | jq -r '.result[0].id // empty'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Add or update DNS record
|
|
120
|
+
cmd_add() {
|
|
121
|
+
local subdomain="$1"
|
|
122
|
+
local ip="$2"
|
|
123
|
+
|
|
124
|
+
if [[ -z "$subdomain" ]]; then
|
|
125
|
+
echo -e "${RED}Error: Subdomain required${NC}"
|
|
126
|
+
echo "Usage: $0 add <subdomain> [ip]"
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [[ -z "$ip" ]]; then
|
|
131
|
+
echo -e "${BLUE}Detecting public IP...${NC}"
|
|
132
|
+
ip=$(get_public_ip)
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
local full_name="${subdomain}.${DOMAIN}"
|
|
136
|
+
echo -e "${BLUE}Managing DNS record for ${full_name} -> ${ip}${NC}"
|
|
137
|
+
|
|
138
|
+
local zone_id
|
|
139
|
+
zone_id=$(get_zone_id)
|
|
140
|
+
|
|
141
|
+
local record_id
|
|
142
|
+
record_id=$(get_record_id "$zone_id" "$subdomain")
|
|
143
|
+
|
|
144
|
+
local data
|
|
145
|
+
data=$(jq -n \
|
|
146
|
+
--arg type "A" \
|
|
147
|
+
--arg name "$full_name" \
|
|
148
|
+
--arg content "$ip" \
|
|
149
|
+
--argjson proxied "$PROXIED" \
|
|
150
|
+
--argjson ttl 1 \
|
|
151
|
+
'{type: $type, name: $name, content: $content, proxied: $proxied, ttl: $ttl}')
|
|
152
|
+
|
|
153
|
+
local response
|
|
154
|
+
if [[ -n "$record_id" ]]; then
|
|
155
|
+
echo -e "${YELLOW}Updating existing record...${NC}"
|
|
156
|
+
response=$(cf_api PUT "/zones/$zone_id/dns_records/$record_id" "$data")
|
|
157
|
+
else
|
|
158
|
+
echo -e "${YELLOW}Creating new record...${NC}"
|
|
159
|
+
response=$(cf_api POST "/zones/$zone_id/dns_records" "$data")
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
if echo "$response" | jq -e '.success' > /dev/null 2>&1; then
|
|
163
|
+
echo -e "${GREEN}DNS record configured:${NC}"
|
|
164
|
+
echo "$response" | jq '{name: .result.name, type: .result.type, content: .result.content, proxied: .result.proxied}'
|
|
165
|
+
else
|
|
166
|
+
echo -e "${RED}Error: Failed to configure DNS record${NC}"
|
|
167
|
+
echo "$response" | jq '.errors' 2>/dev/null || echo "$response"
|
|
168
|
+
exit 1
|
|
169
|
+
fi
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Delete DNS record
|
|
173
|
+
cmd_delete() {
|
|
174
|
+
local subdomain="$1"
|
|
175
|
+
|
|
176
|
+
if [[ -z "$subdomain" ]]; then
|
|
177
|
+
echo -e "${RED}Error: Subdomain required${NC}"
|
|
178
|
+
echo "Usage: $0 delete <subdomain>"
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
local full_name="${subdomain}.${DOMAIN}"
|
|
183
|
+
echo -e "${BLUE}Deleting DNS record for ${full_name}${NC}"
|
|
184
|
+
|
|
185
|
+
local zone_id
|
|
186
|
+
zone_id=$(get_zone_id)
|
|
187
|
+
|
|
188
|
+
local record_id
|
|
189
|
+
record_id=$(get_record_id "$zone_id" "$subdomain")
|
|
190
|
+
|
|
191
|
+
if [[ -z "$record_id" ]]; then
|
|
192
|
+
echo -e "${YELLOW}Record not found: ${full_name}${NC}"
|
|
193
|
+
exit 0
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
local response
|
|
197
|
+
response=$(cf_api DELETE "/zones/$zone_id/dns_records/$record_id")
|
|
198
|
+
|
|
199
|
+
if echo "$response" | jq -e '.success' > /dev/null 2>&1; then
|
|
200
|
+
echo -e "${GREEN}DNS record deleted: ${full_name}${NC}"
|
|
201
|
+
else
|
|
202
|
+
echo -e "${RED}Error: Failed to delete DNS record${NC}"
|
|
203
|
+
echo "$response" | jq '.errors' 2>/dev/null || echo "$response"
|
|
204
|
+
exit 1
|
|
205
|
+
fi
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# List all DNS records
|
|
209
|
+
cmd_list() {
|
|
210
|
+
echo -e "${BLUE}Listing DNS records for ${DOMAIN}${NC}"
|
|
211
|
+
|
|
212
|
+
local zone_id
|
|
213
|
+
zone_id=$(get_zone_id)
|
|
214
|
+
|
|
215
|
+
local response
|
|
216
|
+
response=$(cf_api GET "/zones/$zone_id/dns_records?per_page=100")
|
|
217
|
+
|
|
218
|
+
if echo "$response" | jq -e '.success' > /dev/null 2>&1; then
|
|
219
|
+
echo "$response" | jq -r '.result[] | "\(.type)\t\(.name)\t\(.content)\t\(if .proxied then "proxied" else "dns-only" end)"' | column -t
|
|
220
|
+
else
|
|
221
|
+
echo -e "${RED}Error: Failed to list DNS records${NC}"
|
|
222
|
+
echo "$response" | jq '.errors' 2>/dev/null || echo "$response"
|
|
223
|
+
exit 1
|
|
224
|
+
fi
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# Get specific record
|
|
228
|
+
cmd_get() {
|
|
229
|
+
local subdomain="$1"
|
|
230
|
+
|
|
231
|
+
if [[ -z "$subdomain" ]]; then
|
|
232
|
+
echo -e "${RED}Error: Subdomain required${NC}"
|
|
233
|
+
echo "Usage: $0 get <subdomain>"
|
|
234
|
+
exit 1
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
local full_name="${subdomain}.${DOMAIN}"
|
|
238
|
+
echo -e "${BLUE}Getting DNS record for ${full_name}${NC}"
|
|
239
|
+
|
|
240
|
+
local zone_id
|
|
241
|
+
zone_id=$(get_zone_id)
|
|
242
|
+
|
|
243
|
+
local response
|
|
244
|
+
response=$(cf_api GET "/zones/$zone_id/dns_records?name=$full_name")
|
|
245
|
+
|
|
246
|
+
if echo "$response" | jq -e '.success' > /dev/null 2>&1; then
|
|
247
|
+
local count
|
|
248
|
+
count=$(echo "$response" | jq '.result | length')
|
|
249
|
+
if [[ "$count" -eq 0 ]]; then
|
|
250
|
+
echo -e "${YELLOW}No records found for ${full_name}${NC}"
|
|
251
|
+
else
|
|
252
|
+
echo "$response" | jq '.result[] | {name, type, content, proxied, ttl, created_on, modified_on}'
|
|
253
|
+
fi
|
|
254
|
+
else
|
|
255
|
+
echo -e "${RED}Error: Failed to get DNS record${NC}"
|
|
256
|
+
echo "$response" | jq '.errors' 2>/dev/null || echo "$response"
|
|
257
|
+
exit 1
|
|
258
|
+
fi
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Show usage
|
|
262
|
+
show_usage() {
|
|
263
|
+
echo "Cloudflare DNS Record Manager"
|
|
264
|
+
echo ""
|
|
265
|
+
echo "Usage:"
|
|
266
|
+
echo " $0 add <subdomain> [ip] [--domain <domain>] Add/update A record"
|
|
267
|
+
echo " $0 delete <subdomain> [--domain <domain>] Delete A record"
|
|
268
|
+
echo " $0 list [--domain <domain>] List all DNS records"
|
|
269
|
+
echo " $0 get <subdomain> [--domain <domain>] Get specific record details"
|
|
270
|
+
echo ""
|
|
271
|
+
echo "Options:"
|
|
272
|
+
echo " --domain <domain> Use specified domain (default: pseekoo.io)"
|
|
273
|
+
echo " --no-proxy DNS-only mode (no Cloudflare proxy)"
|
|
274
|
+
echo ""
|
|
275
|
+
echo "Examples:"
|
|
276
|
+
echo " $0 add qdrant Creates qdrant.pseekoo.io"
|
|
277
|
+
echo " $0 add api 1.2.3.4 Creates api.pseekoo.io with specific IP"
|
|
278
|
+
echo " $0 add holylist --domain pseekoo.com Creates holylist.pseekoo.com"
|
|
279
|
+
echo " $0 list --domain pseekoo.com Lists pseekoo.com records"
|
|
280
|
+
echo " $0 delete qdrant Deletes qdrant.pseekoo.io"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# Parse flags from arguments
|
|
284
|
+
FILTERED_ARGS=()
|
|
285
|
+
PROXIED=true
|
|
286
|
+
while [[ $# -gt 0 ]]; do
|
|
287
|
+
case "$1" in
|
|
288
|
+
--domain)
|
|
289
|
+
DOMAIN="$2"
|
|
290
|
+
shift 2
|
|
291
|
+
;;
|
|
292
|
+
--no-proxy)
|
|
293
|
+
PROXIED=false
|
|
294
|
+
shift
|
|
295
|
+
;;
|
|
296
|
+
*)
|
|
297
|
+
FILTERED_ARGS+=("$1")
|
|
298
|
+
shift
|
|
299
|
+
;;
|
|
300
|
+
esac
|
|
301
|
+
done
|
|
302
|
+
|
|
303
|
+
# Main
|
|
304
|
+
case "${FILTERED_ARGS[0]:-}" in
|
|
305
|
+
add)
|
|
306
|
+
cmd_add "${FILTERED_ARGS[1]:-}" "${FILTERED_ARGS[2]:-}"
|
|
307
|
+
;;
|
|
308
|
+
delete)
|
|
309
|
+
cmd_delete "${FILTERED_ARGS[1]:-}"
|
|
310
|
+
;;
|
|
311
|
+
list)
|
|
312
|
+
cmd_list
|
|
313
|
+
;;
|
|
314
|
+
get)
|
|
315
|
+
cmd_get "${FILTERED_ARGS[1]:-}"
|
|
316
|
+
;;
|
|
317
|
+
*)
|
|
318
|
+
show_usage
|
|
319
|
+
exit 1
|
|
320
|
+
;;
|
|
321
|
+
esac
|