Skip to content

Commit 359a2e0

Browse files
authored
Script for Cipher Trust Manager (#404)
* Script for Cipher Trust Manager * Updates: added license activate for new and existing setup. * Updates: Unify cert names b/w kmip scripts, add verbose printing only when required and add --cert-dir as argument now, this will make calling scripts ease of use.
1 parent 7c5de7e commit 359a2e0

File tree

2 files changed

+259
-26
lines changed

2 files changed

+259
-26
lines changed

cipher-kmip-setup.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import requests
2+
import os
3+
import random
4+
import string
5+
import argparse
6+
from urllib.parse import quote
7+
from urllib3.exceptions import InsecureRequestWarning
8+
9+
# Disable SSL warnings for self-signed certificates
10+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
11+
12+
def generate_secure_password(length=12):
13+
"""Generate a secure password with at least one of each character type"""
14+
special_chars = "!@#$%^&*()-_=+"
15+
password_chars = (
16+
random.choice(string.ascii_uppercase) +
17+
random.choice(string.ascii_lowercase) +
18+
random.choice(string.digits) +
19+
random.choice(special_chars) +
20+
''.join(random.choices(string.ascii_letters + string.digits + special_chars, k=length - 4))
21+
)
22+
password_list = list(password_chars)
23+
random.shuffle(password_list)
24+
return ''.join(password_list)
25+
26+
# Parse command line arguments
27+
parser = argparse.ArgumentParser(description='KMIP Client Setup for CipherTrust Manager')
28+
parser.add_argument('--admin-pass', required=True, help='Password for admin user')
29+
parser.add_argument('--ip', required=True, help='CTM IP address')
30+
parser.add_argument('--username', default='testmysql', help='Username to create (default: testmysql)')
31+
parser.add_argument('--client-name', default='mysql-client', help='KMIP client name (default: mysql-client)')
32+
parser.add_argument('--cert-dir', default='./ciper-kmip', help='Certificate store directory (default: ./certs)')
33+
parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output')
34+
args = parser.parse_args()
35+
36+
# Configuration
37+
CTM_IP = args.ip
38+
ADMIN_PASS = args.admin_pass
39+
USERNAME = args.username
40+
USER_PASS = generate_secure_password(12) # Generate secure password
41+
CLIENT_NAME = args.client_name
42+
43+
# Base URL
44+
base_url = f"https://{CTM_IP}/api/v1"
45+
46+
def make_request(method, endpoint, headers=None, data=None):
47+
"""Helper function to make HTTP requests"""
48+
url = f"{base_url}{endpoint}"
49+
response = requests.request(
50+
method=method,
51+
url=url,
52+
headers=headers,
53+
json=data,
54+
verify=False # Skip SSL verification (equivalent to -k flag)
55+
)
56+
return response
57+
58+
# Get authentication token
59+
print("Getting authentication token...")
60+
auth_data = {
61+
"name": "admin",
62+
"password": ADMIN_PASS,
63+
"validity_period": 86400
64+
}
65+
66+
auth_response = make_request("POST", "/auth/tokens/", data=auth_data)
67+
if auth_response.status_code != 200:
68+
raise Exception(f"Authentication failed. Status: {auth_response.status_code}")
69+
70+
jwt_token = auth_response.json()["jwt"]
71+
print("Authentication successful")
72+
73+
# Set up headers with JWT token
74+
headers = {
75+
"Authorization": f"Bearer {jwt_token}",
76+
"Content-Type": "application/json"
77+
}
78+
79+
# Get license information
80+
license_response = make_request("GET", "/licensing/trials/", headers=headers)
81+
license_data = license_response.json()['resources'][0]
82+
83+
license_data = license_response.json()['resources'][0]
84+
license_status = license_data['status']
85+
license_id = license_data['id']
86+
87+
# Activate only if new instance or deactivated state
88+
if license_status in ["deactivated", "available"]:
89+
activate_response = make_request("POST", f"/licensing/trials/{license_id}/activate", headers=headers)
90+
activate_response.raise_for_status()
91+
92+
# Create user
93+
print(f"Creating user: {USERNAME}")
94+
user_data = {
95+
"username": USERNAME,
96+
"password": USER_PASS,
97+
"email": f"{USERNAME}@example.com"
98+
}
99+
100+
user_response = make_request("POST", "/usermgmt/users/", headers=headers, data=user_data)
101+
102+
if user_response.status_code == 201 or user_response.status_code == 200:
103+
user_response_json = user_response.json()
104+
user_id = user_response_json["user_id"]
105+
print("User created successfully")
106+
elif user_response.status_code == 409: # Conflict - user already exists
107+
print("User already exists, fetching user ID...")
108+
# Get user list to find the user ID
109+
users_response = make_request("GET", "/usermgmt/users/", headers=headers)
110+
users_list = users_response.json()
111+
for user in users_list.get("resources", []):
112+
if user.get("username") == USERNAME:
113+
user_id = user.get("user_id")
114+
print("Found existing user")
115+
break
116+
else:
117+
raise Exception(f"Could not find user ID for {USERNAME}")
118+
else:
119+
raise Exception(f"Failed to create user. Status: {user_response.status_code}")
120+
121+
# Assign to Key Admins group
122+
print("Assigning user to Key Admins group...")
123+
# URL encode the user_id since it contains special characters like '|'
124+
encoded_user_id = quote(user_id, safe='')
125+
group_response = make_request("POST", f"/usermgmt/groups/Key%20Admins/users/{encoded_user_id}", headers=headers)
126+
127+
if group_response.status_code not in [200, 201, 204]:
128+
print(f"Warning: Failed to add user to Key Admins group. Status: {group_response.status_code}")
129+
else:
130+
print("Successfully added user to Key Admins group")
131+
132+
# Create profile for KMIP
133+
print("Creating KMIP profile...")
134+
profile_data = {
135+
"name": "mysql",
136+
"subject_dn_field_to_modify": "UID",
137+
"properties": {
138+
"cert_user_field": "CN"
139+
},
140+
"device_credential": {}
141+
}
142+
143+
profile_response = make_request("POST", "/kmip/kmip-profiles", headers=headers, data=profile_data)
144+
145+
# Register the token for KMIP
146+
print("Registering KMIP token...")
147+
token_data = {
148+
"name_prefix": "mysql",
149+
"profile_name": "mysql",
150+
"max_clients": 100
151+
}
152+
153+
token_response = make_request("POST", "/kmip/regtokens/", headers=headers, data=token_data)
154+
if token_response.status_code not in [200, 201]:
155+
raise Exception(f"Token registration failed. Status: {token_response.status_code}")
156+
157+
kmip_token = token_response.json()["token"]
158+
print("KMIP token registered successfully")
159+
160+
# Adjust Interface for MySQL Auth/Cert mode
161+
print("Adjusting KMIP interface mode...")
162+
interface_data = {"mode": "tls-pw-opt"}
163+
interface_response = make_request("PATCH", "/configs/interfaces/kmip", headers=headers, data=interface_data)
164+
165+
# Create the KMIP Client setup using the token
166+
print(f"Creating KMIP client: {CLIENT_NAME}")
167+
client_data = {
168+
"name": CLIENT_NAME,
169+
"reg_token": kmip_token
170+
}
171+
172+
client_response = make_request("POST", "/kmip/kmip-clients", headers=headers, data=client_data)
173+
174+
if client_response.status_code in [200, 201]:
175+
client_resp_json = client_response.json()
176+
print("KMIP client created successfully")
177+
elif client_response.status_code == 409: # Conflict - client already exists
178+
raise Exception(f"KMIP client '{CLIENT_NAME}' already exists. Please change the CLIENT_NAME variable in the script and try again.")
179+
else:
180+
raise Exception(f"KMIP client creation failed. Status: {client_response.status_code}")
181+
182+
# Save certificate files
183+
print("Saving certificate files...")
184+
185+
# Create the certificate directory if it doesn't exist
186+
os.makedirs(args.cert_dir, exist_ok=True)
187+
188+
# Save private key to client_key.pem
189+
key_file_path = os.path.join(args.cert_dir, "client_key.pem")
190+
with open(key_file_path, "w") as key_file:
191+
key_file.write(client_resp_json["key"])
192+
193+
# Save certificate to client_certificate.pem
194+
cert_file_path = os.path.join(args.cert_dir, "client_certificate.pem")
195+
with open(cert_file_path, "w") as cert_file:
196+
cert_file.write(client_resp_json["cert"])
197+
198+
# Save CA certificate to root_certificate.pem
199+
ca_file_path = os.path.join(args.cert_dir, "root_certificate.pem")
200+
with open(ca_file_path, "w") as ca_file:
201+
ca_file.write(client_resp_json["client_ca"])
202+
203+
# Set restrictive permissions on private key file (equivalent to chmod 600)
204+
os.chmod(key_file_path, 0o600)
205+
206+
if args.verbose:
207+
print("KMIP setup completed successfully!")
208+
print(f"Files created within {args.cert_dir}:")
209+
print(f" - Private key: {key_file_path} ")
210+
print(f" - Certificate: {cert_file_path}")
211+
print(f" - CA certificate: {ca_file_path}")

hashicorp-kmip-setup.sh

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,51 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4+
VERBOSE=false
5+
CERTS_DIR="" # Initialize as empty
6+
7+
# Parse arguments
8+
while [[ $# -gt 0 ]]; do
9+
case $1 in
10+
-v|--verbose)
11+
VERBOSE=true
12+
shift
13+
;;
14+
--cert-dir=*|--certs-dir=*)
15+
CERTS_DIR="${1#*=}"
16+
if [[ -z "$CERTS_DIR" ]]; then
17+
echo "Error: --cert-dir= or --certs-dir= requires a directory path"
18+
exit 1
19+
fi
20+
shift
21+
;;
22+
-*)
23+
echo "Unknown option: $1"
24+
exit 1
25+
;;
26+
*)
27+
break
28+
;;
29+
esac
30+
done
31+
432
# Base directory under $HOME
533
VAULT_BASE="${HOME}/vault"
634
CONFIG_DIR="${VAULT_BASE}/config"
735
DATA_DIR="${VAULT_BASE}/data"
836
LOG_DIR="${VAULT_BASE}/log"
9-
CERTS_DIR="${VAULT_BASE}/certs"
37+
CERTS_DIR="${CERTS_DIR:-${VAULT_BASE}/certs}"
1038
VAULT_HCL="${CONFIG_DIR}/vault.hcl"
1139
SCRIPT_DIR="$(pwd)"
1240
VAULT_LICENSE="${SCRIPT_DIR}/vault.hclic"
13-
CONTAINER_NAME="vault-enterprise"
41+
CONTAINER_NAME="kmip_hashicorp"
1442

1543
# Create all necessary directories, and provide permissions for Docker container access.
1644
mkdir -p "${CONFIG_DIR}" "${DATA_DIR}" "${LOG_DIR}" "${CERTS_DIR}"
17-
sudo chown -R 100:1000 ${HOME}/vault/data
18-
sudo chown -R 100:1000 ${HOME}/vault/log
19-
sudo chown -R 100:1000 ${HOME}/vault/certs
45+
sudo chown -R 100:1000 "${CONFIG_DIR}"
46+
sudo chown -R 100:1000 "${DATA_DIR}"
47+
sudo chown -R 100:1000 "${LOG_DIR}"
48+
sudo chown -R 100:1000 "${CERTS_DIR}"
2049

2150
# Ensure license file exists
2251
echo "[INFO] Checking for license in working directory..."
@@ -146,7 +175,7 @@ configure_kmip() {
146175
listen_addrs='0.0.0.0:5696' \
147176
server_hostnames='172.17.0.1'
148177
149-
vault read -field=ca_pem kmip/ca > /vault/certs/vault-kmip-ca.pem
178+
vault read -field=ca_pem kmip/ca > /vault/certs/root_certificate.pem
150179
151180
# Check if scope exists before creating
152181
if ! vault list kmip/scope 2>/dev/null | grep -q my-service 2>/dev/null; then
@@ -162,9 +191,9 @@ configure_kmip() {
162191
vault write -format=json kmip/scope/my-service/role/admin/credential/generate \
163192
format=pem > /vault/certs/credential.json
164193
165-
jq -r '.data.certificate' /vault/certs/credential.json > /vault/certs/mysql-client-cert.pem
166-
jq -r '.data.private_key' /vault/certs/credential.json > /vault/certs/mysql-client-key.pem
167-
"
194+
jq -r '.data.certificate' /vault/certs/credential.json > /vault/certs/client_certificate.pem
195+
jq -r '.data.private_key' /vault/certs/credential.json > /vault/certs/client_key.pem
196+
"
168197
}
169198

170199
verify_kmip_connection() {
@@ -174,9 +203,9 @@ verify_kmip_connection() {
174203

175204
# Add timeout and better error handling
176205
output=$(timeout $timeout openssl s_client -connect 127.0.0.1:5696 \
177-
-CAfile "${CERTS_DIR}/vault-kmip-ca.pem" \
178-
-cert "${CERTS_DIR}/mysql-client-cert.pem" \
179-
-key "${CERTS_DIR}/mysql-client-key.pem" \
206+
-CAfile "${CERTS_DIR}/root_certificate.pem" \
207+
-cert "${CERTS_DIR}/client_certificate.pem" \
208+
-key "${CERTS_DIR}/client_key.pem" \
180209
-showcerts \
181210
-status \
182211
< /dev/null 2>&1)
@@ -196,20 +225,13 @@ main() {
196225
verify_kmip_connection
197226

198227
echo "[INFO] Vault Enterprise deployment successful"
199-
# JSON output
200-
jq -n \
201-
--arg server_addr "127.0.0.1" \
202-
--arg server_port "5696" \
203-
--arg client_ca "${CERTS_DIR}/mysql-client-cert.pem" \
204-
--arg client_key "${CERTS_DIR}/mysql-client-key.pem" \
205-
--arg server_ca "${CERTS_DIR}/vault-kmip-ca.pem" \
206-
'{
207-
server_addr: $server_addr,
208-
server_port: $server_port,
209-
client_ca: $client_ca,
210-
client_key: $client_key,
211-
server_ca: $server_ca
212-
}' | jq '.'
228+
if [ "$VERBOSE" = true ]; then
229+
echo "[INFO] KMIP setup completed successfully!"
230+
echo "[INFO] Files created within $CERTS_DIR:"
231+
echo "[INFO] - Private key: $CERTS_DIR/client_key.pem"
232+
echo "[INFO] - Certificate: $CERTS_DIR/client_certificate.pem"
233+
echo "[INFO] - CA certificate: $CERTS_DIR/root_certificate.pem"
234+
fi
213235
}
214236

215237
main "$@"

0 commit comments

Comments
 (0)