@freshworks/shiftleft-tools 1.1.8

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.
Files changed (106) hide show
  1. package/README.md +351 -0
  2. package/bin/shiftleft.js +95 -0
  3. package/package.json +57 -0
  4. package/src/commands/doctor.js +208 -0
  5. package/src/commands/init-postman.js +298 -0
  6. package/src/commands/init-rules.js +78 -0
  7. package/src/commands/link.js +172 -0
  8. package/src/commands/protect.js +61 -0
  9. package/src/commands/run-tests.js +182 -0
  10. package/src/commands/setup-pipeline.js +209 -0
  11. package/src/commands/update.js +203 -0
  12. package/src/index.js +4 -0
  13. package/src/utils/copy-tree.js +98 -0
  14. package/src/utils/gitignore.js +26 -0
  15. package/src/utils/logger.js +9 -0
  16. package/src/utils/manifest.js +145 -0
  17. package/src/utils/stack.js +80 -0
  18. package/src/utils/template.js +135 -0
  19. package/templates/AGENTS.md +109 -0
  20. package/templates/CLAUDE.md +3 -0
  21. package/templates/jenkins/Jenkinsfile-java.groovy +432 -0
  22. package/templates/jenkins/Jenkinsfile-node.groovy +450 -0
  23. package/templates/postman/.husky/pre-commit +19 -0
  24. package/templates/postman/.prettierrc.json +5 -0
  25. package/templates/postman/README.md.ejs +147 -0
  26. package/templates/postman/collections/01-core.json.ejs +91 -0
  27. package/templates/postman/config/local.json.ejs +12 -0
  28. package/templates/postman/config/staging.json.ejs +26 -0
  29. package/templates/postman/environments/local.postman_environment.json.ejs +31 -0
  30. package/templates/postman/environments/staging.postman_environment.json.ejs +31 -0
  31. package/templates/postman/gitignore +16 -0
  32. package/templates/postman/npmrc +31 -0
  33. package/templates/postman/package.json.ejs +66 -0
  34. package/templates/postman/run-all-shim.sh +16 -0
  35. package/templates/postman/scripts/auth/generate-jwt.sh +113 -0
  36. package/templates/postman/scripts/auth/get-issuer-secret.sh +140 -0
  37. package/templates/postman/scripts/infra/start-mocks.sh +138 -0
  38. package/templates/postman/scripts/infra/stop-mocks.sh +43 -0
  39. package/templates/postman/scripts/lib/api_coverage.py +1122 -0
  40. package/templates/postman/scripts/lib/cleanup-reports.sh +101 -0
  41. package/templates/postman/scripts/lib/cleanup-stryker.sh +44 -0
  42. package/templates/postman/scripts/lib/report_combined.py +527 -0
  43. package/templates/postman/scripts/lib/report_consolidated.py +363 -0
  44. package/templates/postman/scripts/lib/report_generator.py +121 -0
  45. package/templates/postman/scripts/lib/report_migration.py +156 -0
  46. package/templates/postman/scripts/lib/report_mutation.py +110 -0
  47. package/templates/postman/scripts/lib/report_unit.py +353 -0
  48. package/templates/postman/scripts/lib/report_utils.py +973 -0
  49. package/templates/postman/scripts/report-generators/generate-consolidated-report.sh +445 -0
  50. package/templates/postman/scripts/report-generators/java-api-coverage-matrix.sh +257 -0
  51. package/templates/postman/scripts/report-generators/mutation-report.sh +672 -0
  52. package/templates/postman/scripts/report-generators/node-api-coverage-matrix.sh +167 -0
  53. package/templates/postman/scripts/report-generators/stage-report-artifacts.sh +27 -0
  54. package/templates/postman/scripts/run-all.sh +452 -0
  55. package/templates/postman/scripts/runners/run-mutation-tests.sh +113 -0
  56. package/templates/postman/scripts/runners/run-tests-local.sh +936 -0
  57. package/templates/postman/scripts/runners/run-tests-staging.sh +741 -0
  58. package/templates/postman-node/README.md.ejs +26 -0
  59. package/templates/postman-node/collections/crud/01-bootstrap.json.ejs +34 -0
  60. package/templates/postman-node/config/local.json.ejs +46 -0
  61. package/templates/postman-node/config/staging.json.ejs +31 -0
  62. package/templates/postman-node/local.test.env.ejs +3 -0
  63. package/templates/postman-node/mocks/external.js +14 -0
  64. package/templates/postman-node/package.json.ejs +39 -0
  65. package/templates/postman-node/requirements.txt +1 -0
  66. package/templates/postman-node/scripts/database/cleanup-mysql.sh +12 -0
  67. package/templates/postman-node/scripts/database/run-migrations.js +29 -0
  68. package/templates/postman-node/scripts/database/start-mysql.sh +34 -0
  69. package/templates/postman-node/scripts/database/wait-for-mysql.sh +36 -0
  70. package/templates/postman-node/scripts/lib/api_coverage_node.py +1137 -0
  71. package/templates/postman-node/scripts/lib/fetch-jwt.sh +86 -0
  72. package/templates/postman-node/scripts/lib/run-newman.sh +104 -0
  73. package/templates/postman-node/scripts/lib/setup-database.sh +55 -0
  74. package/templates/postman-node/scripts/lib/start-app.sh +48 -0
  75. package/templates/postman-node/scripts/lib/utils.sh +114 -0
  76. package/templates/postman-node/scripts/report-generators/stage-report-artifacts.sh +26 -0
  77. package/templates/postman-node/scripts/run-all.sh +303 -0
  78. package/templates/postman-node/scripts/runners/run-tests.sh +123 -0
  79. package/templates/postman-node/scripts/setup-mocks.js.ejs +29 -0
  80. package/templates/postman-node/stryker.config.js.ejs +51 -0
  81. package/templates/rules/local-test-setup.mdc +420 -0
  82. package/templates/rules/testing-node.mdc +66 -0
  83. package/templates/rules/testing.mdc +248 -0
  84. package/templates/skills/_shared/postman-standards.md +380 -0
  85. package/templates/skills/enhance-test-pipeline/SKILL-java.md +483 -0
  86. package/templates/skills/enhance-test-pipeline/SKILL-node.md +431 -0
  87. package/templates/skills/enhance-test-pipeline/SKILL.md +9 -0
  88. package/templates/skills/review-test-suite/SKILL-java.md +137 -0
  89. package/templates/skills/review-test-suite/SKILL-node.md +78 -0
  90. package/templates/skills/review-test-suite/SKILL.md +9 -0
  91. package/templates/skills/run-test-suite/SKILL-java.md +186 -0
  92. package/templates/skills/run-test-suite/SKILL-node.md +191 -0
  93. package/templates/skills/run-test-suite/SKILL.md +9 -0
  94. package/templates/skills/setup-api-tests/SKILL-java.md +1094 -0
  95. package/templates/skills/setup-api-tests/SKILL-node.md +141 -0
  96. package/templates/skills/setup-api-tests/SKILL.md +9 -0
  97. package/templates/skills/setup-mutation-tests/SKILL-java.md +303 -0
  98. package/templates/skills/setup-mutation-tests/SKILL-node.md +408 -0
  99. package/templates/skills/setup-mutation-tests/SKILL.md +9 -0
  100. package/templates/skills/setup-test-pipeline/SKILL-java.md +454 -0
  101. package/templates/skills/setup-test-pipeline/SKILL-node.md +318 -0
  102. package/templates/skills/setup-test-pipeline/SKILL.md +9 -0
  103. package/templates/skills/write-api-tests/SKILL-java.md +115 -0
  104. package/templates/skills/write-api-tests/SKILL-node.md +83 -0
  105. package/templates/skills/write-api-tests/SKILL.md +9 -0
  106. package/templates/stryker.config.js +50 -0
@@ -0,0 +1,91 @@
1
+ {
2
+ "info": {
3
+ "_postman_id": "01-core-<%= serviceNameLower %>",
4
+ "name": "Core API",
5
+ "description": "Core API endpoints for <%= serviceName %>",
6
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
7
+ },
8
+ "auth": {
9
+ "type": "bearer",
10
+ "bearer": [
11
+ {
12
+ "key": "token",
13
+ "value": "{{auth_token}}",
14
+ "type": "string"
15
+ }
16
+ ]
17
+ },
18
+ "event": [
19
+ {
20
+ "listen": "prerequest",
21
+ "script": {
22
+ "type": "text/javascript",
23
+ "exec": [""]
24
+ }
25
+ },
26
+ {
27
+ "listen": "test",
28
+ "script": {
29
+ "type": "text/javascript",
30
+ "exec": [""]
31
+ }
32
+ }
33
+ ],
34
+ "item": [
35
+ {
36
+ "name": "Health Check",
37
+ "item": [
38
+ {
39
+ "name": "Health Check - 200 OK",
40
+ "event": [
41
+ {
42
+ "listen": "test",
43
+ "script": {
44
+ "exec": [
45
+ "pm.test(\"Status code is 200\", function () {",
46
+ " pm.response.to.have.status(200);",
47
+ "});",
48
+ "",
49
+ "pm.test(\"Response time is less than 2000ms\", function () {",
50
+ " pm.expect(pm.response.responseTime).to.be.below(2000);",
51
+ "});",
52
+ "",
53
+ "pm.test(\"Content-Type is application/json\", function () {",
54
+ " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(\"application/json\");",
55
+ "});",
56
+ "",
57
+ "var jsonData = pm.response.json();",
58
+ "",
59
+ "pm.test(\"Response has status field\", function () {",
60
+ " pm.expect(jsonData).to.have.property('status');",
61
+ " pm.expect(jsonData.status).to.eql('UP');",
62
+ "});"
63
+ ],
64
+ "type": "text/javascript"
65
+ }
66
+ }
67
+ ],
68
+ "request": {
69
+ "method": "GET",
70
+ "header": [],
71
+ "url": {
72
+ "raw": "{{base_url}}/actuator/health",
73
+ "host": ["{{base_url}}"],
74
+ "path": ["actuator", "health"]
75
+ },
76
+ "description": "Health check endpoint to verify service is running"
77
+ },
78
+ "response": []
79
+ }
80
+ ]
81
+ }
82
+ ],
83
+ "variable": [
84
+ {
85
+ "key": "base_url",
86
+ "value": "http://localhost:8080/api",
87
+ "type": "string",
88
+ "description": "Base URL for the API"
89
+ }
90
+ ]
91
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "comment": "Local environment configuration for <%= serviceName %> Postman tests",
3
+ "environment": "local",
4
+ "base_url": "http://localhost:8080/api",
5
+ "jwt": {
6
+ "use_hardcoded_token": true,
7
+ "hardcoded_token": "temporary_token"
8
+ },
9
+ "headers": {
10
+ "content_type": "application/json"
11
+ }
12
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "comment": "Staging environment configuration for <%= serviceName %> Postman tests",
3
+ "environment": "staging",
4
+ "base_url": "https://your-staging-url.freshworks.com/api",
5
+ "aws": {
6
+ "secretName": "staging_stack_secrets",
7
+ "region": "us-west-2",
8
+ "client": "your_client",
9
+ "issuer": "your_issuer"
10
+ },
11
+ "jwt": {
12
+ "use_hardcoded_token": false,
13
+ "api_issuer": "your_issuer",
14
+ "api_account_id": "your_account_id",
15
+ "api_account_domain": "your_domain.freshworks.com",
16
+ "api_user_id": "your_user_id",
17
+ "api_product": "freshdesk",
18
+ "api_product_id": "1",
19
+ "api_user_role": "admin",
20
+ "api_pod": "poduswest2",
21
+ "api_jwt_expiry": "240"
22
+ },
23
+ "headers": {
24
+ "content_type": "application/json"
25
+ }
26
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "local-environment",
3
+ "name": "<%= serviceName %> - Local",
4
+ "values": [
5
+ {
6
+ "key": "base_url",
7
+ "value": "http://localhost:8080/api",
8
+ "type": "default",
9
+ "enabled": true
10
+ },
11
+ {
12
+ "key": "environment",
13
+ "value": "local",
14
+ "type": "default",
15
+ "enabled": true
16
+ },
17
+ {
18
+ "key": "auth_token",
19
+ "value": "temporary_token",
20
+ "type": "default",
21
+ "enabled": true
22
+ },
23
+ {
24
+ "key": "setup_complete",
25
+ "value": "false",
26
+ "type": "default",
27
+ "enabled": true
28
+ }
29
+ ],
30
+ "_postman_variable_scope": "environment"
31
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "staging-environment",
3
+ "name": "<%= serviceName %> - Staging",
4
+ "values": [
5
+ {
6
+ "key": "base_url",
7
+ "value": "https://your-staging-url.freshworks.com/api",
8
+ "type": "default",
9
+ "enabled": true
10
+ },
11
+ {
12
+ "key": "environment",
13
+ "value": "staging",
14
+ "type": "default",
15
+ "enabled": true
16
+ },
17
+ {
18
+ "key": "auth_token",
19
+ "value": "",
20
+ "type": "secret",
21
+ "enabled": true
22
+ },
23
+ {
24
+ "key": "setup_complete",
25
+ "value": "false",
26
+ "type": "default",
27
+ "enabled": true
28
+ }
29
+ ],
30
+ "_postman_variable_scope": "environment"
31
+ }
@@ -0,0 +1,16 @@
1
+ node_modules/
2
+ reports/*.html
3
+ reports/*.json
4
+ reports/json/
5
+ *.log
6
+ audit-report.json
7
+ deps-tree.txt
8
+
9
+ # dev-tools staged scripts (machine-local cache; refreshed by `dev-tools test`)
10
+ scripts/.run-all-impl.sh
11
+ scripts/auth/
12
+ scripts/database/
13
+ scripts/infra/
14
+ scripts/lib/
15
+ scripts/report-generators/
16
+ scripts/runners/
@@ -0,0 +1,31 @@
1
+ # =============================================================================
2
+ # npm Security Configuration for Postman Tests
3
+ # =============================================================================
4
+ #
5
+ # This file configures npm with security-focused settings to reduce the risk
6
+ # of supply chain attacks (like the tunnel-agent compromise).
7
+ #
8
+ # =============================================================================
9
+
10
+ # CRITICAL: Block postinstall attacks
11
+ # This prevents malicious packages from running scripts during installation
12
+ ignore-scripts=true
13
+
14
+ # Lock dependencies to exact versions
15
+ # Ensures reproducible builds and prevents version drift attacks
16
+ package-lock=true
17
+ save-exact=true
18
+
19
+ # Use official npm registry with SSL
20
+ # Prevents man-in-the-middle attacks and package tampering
21
+ registry=https://registry.npmjs.org/
22
+ strict-ssl=true
23
+
24
+ # Run security audit on install
25
+ # Automatically checks for known CVEs in dependencies
26
+ audit=true
27
+ audit-level=high
28
+
29
+ # Verify package signatures (npm 8.4+)
30
+ # Ensures packages haven't been tampered with after publishing
31
+ audit-signatures=true
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "<%= serviceName %>-postman-tests",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Postman API tests for <%= serviceName %> - secure configuration with pinned dependencies",
6
+ "scripts": {
7
+ "prepare": "cd .. && husky postman/.husky",
8
+ "test:local": "./scripts/runners/run-tests-local.sh",<% if (includeStaging) { %>
9
+ "test:staging": "./scripts/runners/run-tests-staging.sh",<% } %>
10
+ "audit": "npm audit --audit-level=high",
11
+ "audit:fix": "npm audit fix",
12
+ "audit:json": "npm audit --json > audit-report.json",
13
+ "audit:signatures": "npm audit signatures",
14
+ "deps:check": "npm ls --all 2>/dev/null | grep -E 'tunnel-agent|@postman|got|node-fetch' || echo 'No problematic packages found'",
15
+ "deps:tree": "npm ls --all",
16
+ "deps:outdated": "npm outdated",
17
+ "deps:duplicates": "npm ls --all 2>/dev/null | sort | uniq -d || echo 'No duplicates'",
18
+ "security:check": "npm run audit && npm run audit:signatures && npm run deps:check",
19
+ "security:full": "npm run audit:json && npm run deps:tree > deps-tree.txt && echo 'Reports: audit-report.json, deps-tree.txt'",
20
+ "format": "prettier --write '**/*.json' '!node_modules/**'",
21
+ "format:check": "prettier --check '**/*.json' '!node_modules/**'"
22
+ },
23
+ "dependencies": {
24
+ "newman": "6.2.2",
25
+ "newman-reporter-htmlextra": "1.23.1"
26
+ },
27
+ "devDependencies": {
28
+ "husky": "9.1.7",
29
+ "prettier": "3.4.2"
30
+ },
31
+ "overrides": {
32
+ "tunnel-agent": "0.6.0",
33
+ "@postman/tunnel-agent": "0.6.3",
34
+ "tough-cookie": "4.1.3",
35
+ "semver": "7.5.4",
36
+ "word-wrap": "1.2.5",
37
+ "xml2js": "0.6.2",
38
+ "http-cache-semantics": "4.1.1",
39
+ "json5": "2.2.3",
40
+ "minimatch": "3.1.2",
41
+ "jose": "4.15.5",
42
+ "node-forge": "1.3.3",
43
+ "qs": "6.15.0",
44
+ "lodash": "4.17.23",
45
+ "ajv": "6.14.0",
46
+ "postman-runtime": {
47
+ "jose": "4.15.5",
48
+ "node-forge": "1.3.3"
49
+ },
50
+ "postman-request": {
51
+ "qs": "6.15.0"
52
+ }
53
+ },
54
+ "engines": {
55
+ "node": ">=18.0.0",
56
+ "npm": ">=8.4.0"
57
+ },
58
+ "keywords": [
59
+ "postman",
60
+ "api-testing",
61
+ "newman",
62
+ "<%= serviceName %>"
63
+ ],
64
+ "author": "Developer Platform Team",
65
+ "license": "UNLICENSED"
66
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # Generated by shiftleft-tools — do not edit.
3
+ #
4
+ # Test orchestration lives in the shiftleft-tools package, not in this repo. This
5
+ # shim stages the latest scripts (gitignored, machine-local) and runs them, so
6
+ # `npm install -g shiftleft-tools` updates behavior with no repo change.
7
+ # All flags pass through unchanged: --env, --skip-unit, --skip-mutation,
8
+ # --skip-postman, --skip-coverage, --skip-report, --no-delay, ...
9
+
10
+ if ! command -v shiftleft >/dev/null 2>&1; then
11
+ echo "error: shiftleft CLI not found." >&2
12
+ echo "Install it with: npm install -g shiftleft-tools --registry=https://nexuscentral.runwayci.com/repository/npm-group/" >&2
13
+ exit 127
14
+ fi
15
+
16
+ exec shiftleft test "$@"
@@ -0,0 +1,113 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # SECURE JWT Generator - Uses only bash and openssl (NO Node.js, NO npm)
4
+ # =============================================================================
5
+ #
6
+ # This script replaces generate-jwt.js to eliminate npm supply chain risk.
7
+ # The ISSUER_SECRET never touches any Node.js process.
8
+ #
9
+ # Usage:
10
+ # ISSUER_SECRET=<secret> ./generate-jwt.sh
11
+ #
12
+ # Or with all parameters:
13
+ # ISSUER_SECRET=<secret> \
14
+ # API_ISSUER=mp_collections_manager \
15
+ # API_ACCOUNT_ID=123 \
16
+ # ./generate-jwt.sh
17
+ #
18
+ # =============================================================================
19
+
20
+ set -euo pipefail
21
+
22
+ # Configuration from environment or defaults
23
+ API_ISSUER="${API_ISSUER:-mp_collections_manager}"
24
+ API_ACCOUNT_ID="${API_ACCOUNT_ID:-1}"
25
+ API_ACCOUNT_DOMAIN="${API_ACCOUNT_DOMAIN:-freshworks.com}"
26
+ API_USER_ID="${API_USER_ID:-1}"
27
+ API_PRODUCT="${API_PRODUCT:-freshdesk}"
28
+ API_PRODUCT_ID="${API_PRODUCT_ID:-1}"
29
+ API_USER_ROLE="${API_USER_ROLE:-internal_admin}"
30
+ API_POD="${API_POD:-poduswest2}"
31
+ API_SKUS="${API_SKUS:-estate_pro}"
32
+ SKU="${SKU:-estate_pro}"
33
+ IS_SOURCE_INTERNAL="${IS_SOURCE_INTERNAL:-false}"
34
+ API_JWT_EXPIRY="${API_JWT_EXPIRY:-3600}"
35
+ ISSUER_SECRET="${ISSUER_SECRET:-}"
36
+
37
+ # Validate required secret
38
+ if [ -z "$ISSUER_SECRET" ]; then
39
+ echo "ERROR: ISSUER_SECRET environment variable is required" >&2
40
+ echo "" >&2
41
+ echo "Usage: ISSUER_SECRET=<secret> $0" >&2
42
+ echo "" >&2
43
+ echo "Or set all parameters:" >&2
44
+ echo " ISSUER_SECRET=<secret> \\" >&2
45
+ echo " API_ISSUER=mp_collections_manager \\" >&2
46
+ echo " API_ACCOUNT_ID=123 \\" >&2
47
+ echo " $0" >&2
48
+ exit 1
49
+ fi
50
+
51
+ # =============================================================================
52
+ # Base64URL encode function (URL-safe base64 without padding)
53
+ # This matches the JWT spec: https://tools.ietf.org/html/rfc7519
54
+ # =============================================================================
55
+ base64url_encode() {
56
+ # Read from stdin, base64 encode, convert to URL-safe format, remove padding
57
+ openssl base64 -A | tr '+/' '-_' | tr -d '='
58
+ }
59
+
60
+ # =============================================================================
61
+ # Build JWT
62
+ # =============================================================================
63
+
64
+ # Current timestamp
65
+ NOW=$(date +%s)
66
+ EXP=$((NOW + API_JWT_EXPIRY))
67
+
68
+ # JWT Header (always the same for HS256)
69
+ HEADER='{"alg":"HS256","typ":"JWT"}'
70
+
71
+ # JWT Payload - build dynamically to only include non-empty fields
72
+ PAYLOAD="{"
73
+ PAYLOAD+="\"iss\":\"${API_ISSUER}\""
74
+
75
+ [ -n "$API_ACCOUNT_ID" ] && PAYLOAD+=",\"account_id\":\"${API_ACCOUNT_ID}\""
76
+ [ -n "$API_ACCOUNT_DOMAIN" ] && PAYLOAD+=",\"account_domain\":\"${API_ACCOUNT_DOMAIN}\""
77
+ [ -n "$API_USER_ID" ] && PAYLOAD+=",\"user_id\":\"${API_USER_ID}\""
78
+ [ -n "$API_PRODUCT" ] && PAYLOAD+=",\"product\":\"${API_PRODUCT}\""
79
+ [ -n "$API_PRODUCT_ID" ] && PAYLOAD+=",\"product_id\":\"${API_PRODUCT_ID}\""
80
+ [ -n "$API_USER_ROLE" ] && PAYLOAD+=",\"user_role\":\"${API_USER_ROLE}\""
81
+ [ -n "$API_POD" ] && PAYLOAD+=",\"pod\":\"${API_POD}\""
82
+ [ -n "$API_SKUS" ] && PAYLOAD+=",\"skus\":\"${API_SKUS}\""
83
+ [ -n "$SKU" ] && PAYLOAD+=",\"sku\":\"${SKU}\""
84
+
85
+ # Boolean field
86
+ if [ "$IS_SOURCE_INTERNAL" = "true" ]; then
87
+ PAYLOAD+=",\"is_source_internal\":true"
88
+ fi
89
+
90
+ # Timestamps
91
+ PAYLOAD+=",\"iat\":${NOW}"
92
+ PAYLOAD+=",\"exp\":${EXP}"
93
+ PAYLOAD+="}"
94
+
95
+ # =============================================================================
96
+ # Encode and Sign
97
+ # =============================================================================
98
+
99
+ # Encode header and payload
100
+ HEADER_B64=$(echo -n "$HEADER" | base64url_encode)
101
+ PAYLOAD_B64=$(echo -n "$PAYLOAD" | base64url_encode)
102
+
103
+ # Create signature using HMAC-SHA256
104
+ # This is the secure part - openssl handles the cryptography
105
+ SIGNATURE=$(echo -n "${HEADER_B64}.${PAYLOAD_B64}" | \
106
+ openssl dgst -sha256 -hmac "$ISSUER_SECRET" -binary | \
107
+ base64url_encode)
108
+
109
+ # =============================================================================
110
+ # Output complete JWT
111
+ # =============================================================================
112
+ echo "${HEADER_B64}.${PAYLOAD_B64}.${SIGNATURE}"
113
+
@@ -0,0 +1,140 @@
1
+ #!/bin/bash
2
+
3
+ # Helper script to fetch ISSUER_SECRET from AWS Secrets Manager
4
+ #
5
+ # Usage:
6
+ # ./get-issuer-secret.sh <secret-name> <client> <issuer> [region]
7
+ #
8
+ # Authentication:
9
+ # - In Jenkins/CI: Uses IAM role attached to Kubernetes pod (no profile needed)
10
+ # - Locally: Set AWS_PROFILE environment variable (e.g., export AWS_PROFILE=staging)
11
+ #
12
+ # Example:
13
+ # AWS_PROFILE=staging ./get-issuer-secret.sh staging_stack_secrets developer_platform mp_collections_manager us-west-2
14
+
15
+ set -e
16
+
17
+ # Colors
18
+ GREEN='\033[0;32m'
19
+ RED='\033[0;31m'
20
+ YELLOW='\033[1;33m'
21
+ BLUE='\033[0;34m'
22
+ NC='\033[0m'
23
+
24
+ # Parse arguments
25
+ SECRET_NAME="${1:-}"
26
+ CLIENT="${2:-}"
27
+ ISSUER="${3:-}"
28
+ REGION="${4:-us-east-1}"
29
+
30
+ # Show usage if arguments missing
31
+ if [ -z "$SECRET_NAME" ] || [ -z "$CLIENT" ] || [ -z "$ISSUER" ]; then
32
+ echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}"
33
+ echo -e "${BLUE}║ Get Issuer Secret from AWS Secrets Manager ║${NC}"
34
+ echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}"
35
+ echo ""
36
+ echo "Usage: $0 <secret-name> <client> <issuer> [region]"
37
+ echo ""
38
+ echo "Arguments:"
39
+ echo " secret-name AWS Secrets Manager secret name"
40
+ echo " client Client name (e.g., developer_platform)"
41
+ echo " issuer Issuer name (e.g., developer_platform)"
42
+ echo " region AWS region (default: us-east-1)"
43
+ echo ""
44
+ echo "Example:"
45
+ echo " $0 my-secret-name developer_platform developer_platform us-east-1"
46
+ echo ""
47
+ echo "Output:"
48
+ echo " Prints the issuer secret to stdout"
49
+ echo ""
50
+ exit 1
51
+ fi
52
+
53
+ # Check if AWS CLI is installed
54
+ if ! command -v aws &> /dev/null; then
55
+ echo -e "${RED}✗ Error: AWS CLI is not installed${NC}"
56
+ echo ""
57
+ echo "Install AWS CLI:"
58
+ echo " https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"
59
+ echo ""
60
+ exit 1
61
+ fi
62
+
63
+ # Check if jq is installed
64
+ if ! command -v jq &> /dev/null; then
65
+ echo -e "${RED}✗ Error: jq is not installed${NC}"
66
+ echo ""
67
+ echo "Install jq:"
68
+ echo " brew install jq # macOS"
69
+ echo " apt install jq # Ubuntu/Debian"
70
+ echo ""
71
+ exit 1
72
+ fi
73
+
74
+ # Fetch secret from AWS
75
+ # Disable exit-on-error to capture error details for better diagnostics
76
+ set +e
77
+
78
+ # Use --profile if AWS_PROFILE is set, otherwise use default credentials (IAM role in Jenkins)
79
+ if [ -n "${AWS_PROFILE:-}" ]; then
80
+ SECRET_JSON=$(aws --profile "$AWS_PROFILE" secretsmanager get-secret-value \
81
+ --secret-id "$SECRET_NAME" \
82
+ --region "$REGION" \
83
+ --query SecretString \
84
+ --output text 2>&1)
85
+ else
86
+ SECRET_JSON=$(aws secretsmanager get-secret-value \
87
+ --secret-id "$SECRET_NAME" \
88
+ --region "$REGION" \
89
+ --query SecretString \
90
+ --output text 2>&1)
91
+ fi
92
+
93
+ AWS_EXIT_CODE=$?
94
+ set -e
95
+
96
+ if [ $AWS_EXIT_CODE -ne 0 ]; then
97
+ echo -e "${RED}✗ Error fetching secret from AWS:${NC}" >&2
98
+ echo "$SECRET_JSON" >&2
99
+ echo "" >&2
100
+
101
+ # Check if it's a credentials issue
102
+ if echo "$SECRET_JSON" | grep -q "Unable to locate credentials"; then
103
+ echo -e "${YELLOW}AWS credentials not configured!${NC}" >&2
104
+ echo "" >&2
105
+ echo "Please configure AWS credentials:" >&2
106
+ echo " aws configure" >&2
107
+ echo "" >&2
108
+ echo "You'll need:" >&2
109
+ echo " - AWS Access Key ID" >&2
110
+ echo " - AWS Secret Access Key" >&2
111
+ echo " - Default region: $REGION" >&2
112
+ echo "" >&2
113
+ fi
114
+ exit 1
115
+ fi
116
+
117
+ # For mp-apps API, use jwt_secret_secondary at client level
118
+ # This matches the BaseTest.prototype.mpappsAPISecret implementation
119
+ ISSUER_SECRET=$(echo "$SECRET_JSON" | jq -r ".auth.clients[\"$CLIENT\"].jwt_secret_secondary // empty" 2>/dev/null)
120
+
121
+ # Fall back to primary jwt_secret if secondary not found
122
+ if [ -z "$ISSUER_SECRET" ] || [ "$ISSUER_SECRET" == "null" ]; then
123
+ echo -e "${YELLOW}⚠ Secondary secret not found, falling back to primary client secret${NC}" >&2
124
+ ISSUER_SECRET=$(echo "$SECRET_JSON" | jq -r ".auth.clients[\"$CLIENT\"].jwt_secret // empty" 2>/dev/null)
125
+ fi
126
+
127
+ # Check if secret was found
128
+ if [ -z "$ISSUER_SECRET" ] || [ "$ISSUER_SECRET" == "null" ]; then
129
+ echo -e "${RED}✗ Error: Could not find secret for client '$CLIENT' and issuer '$ISSUER'${NC}" >&2
130
+ echo "" >&2
131
+ echo "Available clients:" >&2
132
+ echo "$SECRET_JSON" | jq -r '.auth.clients | keys[]' 2>/dev/null >&2
133
+ exit 1
134
+ fi
135
+
136
+ # Output the secret (to stdout for easy capture)
137
+ echo -e "${GREEN}✓ Secret found!${NC}" >&2
138
+ echo "" >&2
139
+ echo "$ISSUER_SECRET"
140
+