@bostonuniversity/buwp-local 0.4.0 → 0.5.0

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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "projectName": "buwp-local",
3
+ "image": "ghcr.io/bu-ist/bu-wp-docker-mod_shib:arm64-latest",
4
+ "hostname": "jaydub.local",
5
+ "multisite": true,
6
+ "services": {
7
+ "redis": true,
8
+ "s3proxy": true,
9
+ "shibboleth": true
10
+ },
11
+ "ports": {
12
+ "http": 80,
13
+ "https": 443,
14
+ "db": 3306,
15
+ "redis": 6379
16
+ },
17
+ "mappings": [],
18
+ "env": {
19
+ "WP_DEBUG": true,
20
+ "XDEBUG": false
21
+ }
22
+ }
@@ -0,0 +1,26 @@
1
+ # BU WordPress Local Development Environment Variables
2
+ # Copy this file to .env.local and fill in your actual credentials
3
+ # NEVER commit .env.local to version control!
4
+
5
+ # Database Credentials
6
+ WORDPRESS_DB_PASSWORD=password
7
+ DB_ROOT_PASSWORD=rootpassword
8
+
9
+ # Shibboleth Configuration (if enabled)
10
+ SP_ENTITY_ID=https://your-sp-entity-id
11
+ IDP_ENTITY_ID=https://shib-test.bu.edu/idp/shibboleth
12
+ SHIB_IDP_LOGOUT=https://shib-test.bu.edu/idp/logout.jsp
13
+ SHIB_SP_KEY=your-private-key-here
14
+ SHIB_SP_CERT=your-certificate-here
15
+
16
+ # AWS S3 Configuration (if enabled)
17
+ S3_UPLOADS_BUCKET=your-bucket-name
18
+ S3_UPLOADS_REGION=us-east-1
19
+ S3_UPLOADS_ACCESS_KEY_ID=AKIA...
20
+ S3_UPLOADS_SECRET_ACCESS_KEY=your-secret-key
21
+ S3_ACCESS_RULES_TABLE=your-access-rules-table
22
+
23
+ # OLAP Configuration (if using S3 Object Lambda)
24
+ OLAP=your-olap-name
25
+ OLAP_ACCT_NBR=123456789
26
+ OLAP_REGION=us-east-1
@@ -0,0 +1,240 @@
1
+ # v0.5.0 Phase 3: Hybrid Secure Temp File Integration
2
+
3
+ ## Overview
4
+ Completed implementation of credential loading from macOS keychain into Docker Compose via secure temporary environment files. This is the final integration step of v0.5.0 keychain feature.
5
+
6
+ ## What Was Implemented
7
+
8
+ ### 1. **Credential Loading Functions** (`lib/config.js`)
9
+
10
+ Added three new exported functions:
11
+
12
+ #### `loadKeychainCredentials()`
13
+ - Retrieves all 15 stored credentials from macOS keychain
14
+ - Returns empty object on non-macOS platforms (graceful degradation)
15
+ - Checks `isPlatformSupported()` before attempting keychain access
16
+ - Iterates through `CREDENTIAL_KEYS` constant to load each credential
17
+
18
+ #### `createSecureTempEnvFile(credentials, projectName)`
19
+ - Creates `/tmp/.buwp-local-{projectName}-{timestamp}.env` file
20
+ - Uses atomic file creation with exclusive write flag (`'wx'`)
21
+ - Sets restrictive permissions to user read/write only (`0o600`)
22
+ - Properly escapes multiline credentials:
23
+ - Backslashes: `\\` → `\\\\\\\\`
24
+ - Double quotes: `"` → `\"`
25
+ - Newlines: `\n` → `\\n`
26
+ - Ensures shell interpretation works correctly for complex values (SHIB_SP_KEY, SHIB_SP_CERT)
27
+ - Returns path to created temp file
28
+
29
+ #### `secureDeleteTempEnvFile(filePath)`
30
+ - Overwrites file content with zeros before deletion (defense against forensic recovery)
31
+ - Unlinks file after secure overwrite
32
+ - Returns boolean success status
33
+ - Non-fatal errors - logs warnings but doesn't throw (temp files are ephemeral)
34
+
35
+ ### 2. **Start Command Integration** (`lib/commands/start.js`)
36
+
37
+ Updated `startCommand()` function to:
38
+
39
+ 1. **Import new functions** from config.js
40
+ 2. **Load keychain credentials** after generating compose file:
41
+ ```javascript
42
+ const keychainCredentials = loadKeychainCredentials();
43
+ const keychainCredCount = Object.keys(keychainCredentials).length;
44
+ ```
45
+
46
+ 3. **Create secure temp file** if credentials available:
47
+ ```javascript
48
+ if (keychainCredCount > 0) {
49
+ tempEnvPath = createSecureTempEnvFile(keychainCredentials, projectName);
50
+ }
51
+ ```
52
+
53
+ 4. **Layer environment files** in Docker Compose command:
54
+ ```bash
55
+ docker compose -p {projectName} --env-file {tempEnvFile} --env-file {.env.local} -f {composePath} up -d
56
+ ```
57
+
58
+ 5. **Ensure cleanup** with try/finally block:
59
+ - Cleanup happens even if Docker Compose fails
60
+ - Temp file securely deleted after containers start
61
+
62
+ ### 3. **Credential Precedence**
63
+
64
+ Docker Compose loads env files left-to-right with later files overriding earlier ones:
65
+
66
+ ```
67
+ Keychain Credentials (--env-file /tmp/.buwp-local-*.env)
68
+ ↓ [overridden by]
69
+ .env.local Project Overrides (--env-file .env.local)
70
+ ↓ [overridden by]
71
+ docker-compose.yml Environment Defaults
72
+ ```
73
+
74
+ **Precedence Order (highest to lowest):**
75
+ 1. `.env.local` - Project-specific credential overrides
76
+ 2. Keychain - Global default credentials (all 15 keys)
77
+ 3. docker-compose.yml - Hardcoded defaults
78
+
79
+ ## Security Architecture
80
+
81
+ ### Threat Model: Process List Visibility
82
+ **Original concern:** Won't credentials be visible in `ps auxe`?
83
+
84
+ **Answer:** No. With this approach:
85
+ - Docker command shows: `docker compose --env-file /tmp/.buwp-local-test-project-1234567890.env ...`
86
+ - Credentials are in the file, NOT in the process command line
87
+ - Process list shows only the file path (which is harmless)
88
+
89
+ ### Threat Model: Filesystem Exposure
90
+ **Risk:** Brief window where unencrypted credentials are on disk
91
+
92
+ **Mitigations:**
93
+ 1. **Atomic creation** - File created exclusively with perms set atomically (no TOCTOU window)
94
+ 2. **Restrictive permissions** - `0o600` (user read/write only, no group/others access)
95
+ 3. **Ephemeral location** - `/tmp` directory (typically in-memory on modern systems, auto-cleaned)
96
+ 4. **Secure deletion** - Overwrite with zeros before unlinking (prevents forensic recovery)
97
+ 5. **Short lifetime** - File exists only during `docker compose up` command (~1-2 seconds)
98
+ 6. **Guaranteed cleanup** - try/finally ensures deletion even if Docker fails
99
+
100
+ ### Why Not Process Environment Variables?
101
+ - **Worse security:** All env vars visible in `ps auxe` output to any user on system
102
+ - **Less stable:** Large multiline values (40+ lines) cause issues
103
+ - **Less standard:** Breaks Docker Compose file-based patterns
104
+
105
+ ### Why Not Store Credentials in .env.local?
106
+ - **Already doing this** - `.env.local` is still supported and takes precedence
107
+ - **One-time setup** - Keychain eliminates need to copy credentials to every project
108
+ - **Secure storage** - Keychain uses macOS Secure Enclave on newer Macs
109
+
110
+ ## Integration Flow
111
+
112
+ ```
113
+ 1. Start command runs
114
+
115
+ 2. Load config from .buwp-local.json
116
+
117
+ 3. Load keychain credentials (15 total)
118
+
119
+ 4. Create temp env file (/tmp/.buwp-local-{project}-{timestamp}.env)
120
+ - Atomic creation with 0o600 permissions
121
+ - Properly escaped multiline values
122
+
123
+ 5. Build Docker Compose command with layered env files
124
+ - --env-file /tmp/.buwp-local-{project}-{timestamp}.env
125
+ - --env-file .env.local (if exists)
126
+
127
+ 6. Execute Docker Compose up
128
+
129
+ 7. Clean up temp file (try/finally)
130
+ - Overwrite with zeros
131
+ - Unlink
132
+
133
+ 8. Success message + usage tips
134
+ ```
135
+
136
+ ## Credentials Loaded
137
+
138
+ All 15 credentials from keychain are available:
139
+
140
+ **Database (2):**
141
+ - WORDPRESS_DB_PASSWORD
142
+ - DB_ROOT_PASSWORD
143
+
144
+ **Shibboleth (5):**
145
+ - SP_ENTITY_ID
146
+ - IDP_ENTITY_ID
147
+ - SHIB_IDP_LOGOUT
148
+ - SHIB_SP_KEY (40+ lines)
149
+ - SHIB_SP_CERT (24+ lines)
150
+
151
+ **S3 (5):**
152
+ - S3_UPLOADS_BUCKET
153
+ - S3_UPLOADS_REGION
154
+ - S3_UPLOADS_ACCESS_KEY_ID
155
+ - S3_UPLOADS_SECRET_ACCESS_KEY
156
+ - S3_ACCESS_RULES_TABLE
157
+
158
+ **OLAP (3):**
159
+ - OLAP
160
+ - OLAP_ACCT_NBR
161
+ - OLAP_REGION
162
+
163
+ ## Testing Results
164
+
165
+ Automated integration test verification:
166
+
167
+ ✓ Load 15 credentials from keychain
168
+ ✓ Create secure temp env file with 0o600 permissions
169
+ ✓ Format env file with proper KEY="VALUE" quoting
170
+ ✓ Handle multiline value escaping correctly
171
+ ✓ Overwrite file with zeros before deletion
172
+ ✓ Securely delete temp file
173
+
174
+ All tests passed successfully.
175
+
176
+ ## Version Update
177
+
178
+ Updated package.json version:
179
+ - Previous: v0.4.1
180
+ - Current: v0.5.0
181
+
182
+ ## Code Quality
183
+
184
+ All code passes ESLint checks with no errors or warnings.
185
+
186
+ ## Next Steps
187
+
188
+ Possible future enhancements (not in v0.5.0 scope):
189
+
190
+ 1. **Phase 4: Init Command Enhancements**
191
+ - Auto-populate `buwp-local setup --file` from existing `.env` files
192
+ - Suggest keychain import during `init` command
193
+
194
+ 2. **Phase 5: Global Registry**
195
+ - `~/.buwp-local/registry.json` tracking which projects have credentials
196
+ - Dashboard showing all stored credentials across projects
197
+
198
+ 3. **Phase 6: Cross-Platform Support**
199
+ - Windows DPAPI support (via `credential` PowerShell module)
200
+ - Linux pass/secretservice support
201
+ - Fallback to encrypted `.buwp-local/.env.encrypted` files
202
+
203
+ ## Architecture Summary
204
+
205
+ **Hybrid Secure Temp File Approach:**
206
+ - **Security:** Process list hidden + filesystem defenses
207
+ - **Stability:** Aligns with Docker Compose standard patterns
208
+ - **Usability:** Intuitive file-based precedence and override mechanism
209
+ - **Compatibility:** Maintains existing `.env.local` behavior
210
+
211
+ **Key Decision Rationale:**
212
+ Process list visibility is worse attack vector than brief filesystem window with defense-in-depth mitigations (atomic creation, 600 perms, /tmp location, overwrite-before-delete).
213
+
214
+ ## Files Modified
215
+
216
+ 1. `lib/config.js` (+80 lines)
217
+ - Added keychain import
218
+ - Added 3 new exported functions
219
+ - No breaking changes to existing exports
220
+
221
+ 2. `lib/commands/start.js` (+25 lines)
222
+ - Updated imports to include 3 new functions
223
+ - Added credential loading logic
224
+ - Added temp file creation and cleanup
225
+ - No breaking changes to command interface
226
+
227
+ 3. `package.json`
228
+ - Updated version to 0.5.0
229
+
230
+ ## Status Summary
231
+
232
+ **v0.5.0 Completion Status:**
233
+ - ✅ Phase 1: Keychain core module (v0.5.0)
234
+ - ✅ Phase 2: JSON bulk import (v0.5.0)
235
+ - ✅ Phase 3: Hybrid secure temp file integration (v0.5.0) ← COMPLETED THIS SESSION
236
+ - ⏭️ Phase 4: Init command enhancements (v0.6.0)
237
+ - ⏭️ Phase 5: Global registry (v0.7.0)
238
+ - ⏭️ Phase 6: Cross-platform support (v1.0.0+)
239
+
240
+ **v0.5.0 is now feature-complete and ready for testing.**
@@ -0,0 +1,140 @@
1
+ # Keychain Integration - Implementation Summary
2
+
3
+ ## Overview
4
+
5
+ Successfully implemented macOS Keychain integration for secure credential storage in buwp-local v0.5.0. This allows users to store credentials once and have them automatically available across all projects.
6
+
7
+ ## Files Created
8
+
9
+ ### 1. `lib/keychain.js` - Core Keychain Module
10
+ **Functions:**
11
+ - `setCredential(key, value)` - Store credential in keychain
12
+ - `getCredential(key)` - Retrieve credential from keychain
13
+ - `hasCredential(key)` - Check if credential exists
14
+ - `deleteCredential(key)` - Remove credential
15
+ - `listCredentials()` - Get all stored credential keys
16
+ - `clearAllCredentials()` - Remove all buwp-local credentials
17
+ - `isPlatformSupported()` - Check for macOS platform
18
+
19
+ **Constants:**
20
+ - `CREDENTIAL_KEYS` - All 15 credential keys
21
+ - `CREDENTIAL_GROUPS` - Organized by category (database, shibboleth, s3, olap)
22
+ - `CREDENTIAL_DESCRIPTIONS` - Human-readable descriptions for prompts
23
+
24
+ **Implementation Details:**
25
+ - Uses macOS `security` command via `execSync`
26
+ - Service name: `buwp-local`
27
+ - Uses `-U` flag to update existing credentials instead of duplicating
28
+ - Graceful error handling with user-friendly messages
29
+
30
+ ### 2. `lib/commands/keychain.js` - CLI Command Handler
31
+ **Subcommands:**
32
+ - `setup` - Interactive setup for all credentials
33
+ - `set <key> [value]` - Set individual credential
34
+ - `get <key>` - Retrieve credential (masked display)
35
+ - `list` - Show all stored credentials by group
36
+ - `clear` - Remove all credentials (with confirmation)
37
+ - `status` - Show platform support and credential status
38
+
39
+ **Features:**
40
+ - Interactive prompts using `prompts` package
41
+ - Grouped credential display (database, shibboleth, s3, olap)
42
+ - Existing credential detection with overwrite warnings
43
+ - Force flag support for automation
44
+ - macOS keychain access warning at start of setup
45
+
46
+ ## Files Modified
47
+
48
+ ### 3. `bin/buwp-local.js`
49
+ - Added import for `keychainCommand`
50
+ - Registered `keychain` command with subcommands
51
+ - Added `-f, --force` option support
52
+
53
+ ### 4. `lib/index.js`
54
+ - Exported all keychain functions
55
+ - Exported credential schema constants
56
+
57
+ ## Usage Examples
58
+
59
+ ### First-Time Setup
60
+ ```bash
61
+ # Interactive setup (all credentials)
62
+ buwp-local keychain setup
63
+
64
+ # Set individual credential
65
+ buwp-local keychain set WORDPRESS_DB_PASSWORD mypassword
66
+
67
+ # Check status
68
+ buwp-local keychain status
69
+ ```
70
+
71
+ ### Managing Credentials
72
+ ```bash
73
+ # List stored credentials
74
+ buwp-local keychain list
75
+
76
+ # Get credential value (masked)
77
+ buwp-local keychain get WORDPRESS_DB_PASSWORD
78
+
79
+ # Clear all credentials
80
+ buwp-local keychain clear
81
+ # Or skip confirmation:
82
+ buwp-local keychain clear --force
83
+ ```
84
+
85
+ ## Testing Results
86
+
87
+ ✅ All functions tested and working:
88
+ - Platform detection (macOS supported ✓)
89
+ - Set credential via command line
90
+ - Get credential with masking (test*****word)
91
+ - List credentials by group
92
+ - Clear all credentials
93
+ - Linting passes cleanly
94
+
95
+ ## User Experience
96
+
97
+ ### Warnings & Messages
98
+ - **Platform check**: Non-macOS users see clear message about .env.local fallback
99
+ - **Keychain access**: Users warned about macOS permission prompt before setup
100
+ - **Overwrite protection**: Warns when overwriting existing credentials
101
+ - **Grouped display**: Credentials organized by functional category
102
+
103
+ ### Security Features
104
+ - Credentials never displayed in full (masked in `get` command)
105
+ - Stored securely in macOS keychain (encrypted by OS)
106
+ - Individual credential deletion supported
107
+ - Bulk clear with confirmation (unless --force)
108
+
109
+ ## Design Decisions
110
+
111
+ 1. **Full credential set required** - `setup` prompts for all 15 credentials to ensure complete stack functionality
112
+ 2. **Warning before overwrite** - Users must confirm when updating existing credentials (unless --force)
113
+ 3. **Sparse error messages** - Simple, clear error reporting without over-engineering
114
+ 4. **macOS-only for now** - Platform detection with graceful fallback to .env.local on other systems
115
+
116
+ ## Next Steps (v0.5.0 continued)
117
+
118
+ **Remaining work:**
119
+ 1. Update `start.js` to load credentials from keychain
120
+ 2. Update `init.js` to detect keychain credentials
121
+ 3. Implement credential resolution priority: `.env.local` → keychain → defaults
122
+ 4. Create global registry (`~/.buwp-local/registry.json`)
123
+ 5. Update documentation
124
+
125
+ **Future enhancements (v0.6.0+):**
126
+ - Cross-platform support (Windows Credential Manager, Linux Secret Service)
127
+ - Project-specific credential overrides
128
+ - Credential import/export
129
+ - Team credential sharing mechanisms
130
+
131
+ ## Benefits Delivered
132
+
133
+ ✅ **One-time setup** - Enter credentials once, use across all projects
134
+ ✅ **Secure storage** - Leverages macOS keychain encryption
135
+ ✅ **Simple CLI** - Intuitive subcommands for all operations
136
+ ✅ **Backward compatible** - .env.local still works as override
137
+ ✅ **Developer friendly** - Clear messages and helpful status command
138
+ ✅ **Production ready** - Fully tested and linted
139
+
140
+ This implementation solves the multi-repo credential fatigue problem while maintaining backward compatibility and providing a secure, native macOS integration.
package/USAGE.md CHANGED
@@ -51,7 +51,13 @@ Edit `.buwp-local.json` to map your local repository into the container:
51
51
 
52
52
  ### 4. Create `.env.local` for secrets
53
53
 
54
- Create `.env.local` (never commit this file!):
54
+ Create `.env.local` (never commit this file!) or copy from the example:
55
+
56
+ ```bash
57
+ cp .env.local.example .env.local
58
+ ```
59
+
60
+ Then edit `.env.local` with your actual credentials:
55
61
 
56
62
  ```bash
57
63
  # Database
@@ -70,6 +76,7 @@ S3_UPLOADS_BUCKET=your-bucket
70
76
  S3_UPLOADS_REGION=us-east-1
71
77
  S3_UPLOADS_ACCESS_KEY_ID=your-access-key
72
78
  S3_UPLOADS_SECRET_ACCESS_KEY=your-secret-key
79
+ S3_ACCESS_RULES_TABLE=your-access-rules-table
73
80
 
74
81
  # OLAP
75
82
  OLAP=your-olap-name
@@ -77,6 +84,8 @@ OLAP_ACCT_NBR=your-account-number
77
84
  OLAP_REGION=us-east-1
78
85
  ```
79
86
 
87
+ **Important:** Credentials are read directly from `.env.local` by Docker Compose. They are never written to the generated `docker-compose.yml` file, making it safe to review or share the generated compose file without exposing secrets.
88
+
80
89
  ### 5. Add hostname to /etc/hosts
81
90
 
82
91
  ```bash
@@ -226,10 +235,27 @@ Map your local code into the container:
226
235
  ## Security Best Practices
227
236
 
228
237
  1. **Never commit `.env.local`** - This file contains secrets
229
- 2. **Never commit `.buwp-local/`** - This contains generated files
230
- 3. **Do commit `.buwp-local.json`** - This is your configuration template
231
- 4. **Use environment variables for all secrets**
232
- 5. **Consider using macOS Keychain** for even better security (see docs)
238
+ 2. **Never commit `.buwp-local/`** - This directory contains generated files
239
+ 3. **Do commit `.buwp-local.json`** - This is your configuration template (no secrets)
240
+ 4. **Use environment variables for all secrets** - Credentials stay in `.env.local` and are never written to generated files
241
+ 5. **Review generated compose files** - The generated `docker-compose.yml` only contains variable references like `${WORDPRESS_DB_PASSWORD}`, not actual credentials
242
+ 6. **Copy `.env.local.example`** - Use the provided template to create your `.env.local` file
243
+ 7. **Consider using macOS Keychain** for even better security (future feature)
244
+
245
+ ### How Credentials Work
246
+
247
+ buwp-local uses Docker Compose's native environment variable interpolation:
248
+
249
+ 1. **You provide** credentials in `.env.local`
250
+ 2. **buwp-local generates** `docker-compose.yml` with variable references like `${VAR_NAME}`
251
+ 3. **Docker Compose reads** `.env.local` at runtime and substitutes the values
252
+ 4. **Your secrets never** get written to any generated files
253
+
254
+ This means:
255
+ - ✅ Generated compose files are safe to review or share
256
+ - ✅ No credentials in version control (even accidentally)
257
+ - ✅ Industry-standard Docker Compose pattern
258
+ - ✅ Simple and secure
233
259
 
234
260
  ## File Structure
235
261
 
package/bin/buwp-local.js CHANGED
@@ -29,6 +29,7 @@ import wpCommand from '../lib/commands/wp.js';
29
29
  import shellCommand from '../lib/commands/shell.js';
30
30
  import configCommand from '../lib/commands/config.js';
31
31
  import initCommand from '../lib/commands/init.js';
32
+ import keychainCommand from '../lib/commands/keychain.js';
32
33
 
33
34
  const program = new Command();
34
35
 
@@ -103,6 +104,17 @@ program
103
104
  .description('Open an interactive bash shell in the WordPress container')
104
105
  .action(shellCommand);
105
106
 
107
+ // Keychain command
108
+ program
109
+ .command('keychain <subcommand> [args...]')
110
+ .description('Manage credentials in macOS keychain')
111
+ .option('-f, --force', 'Skip confirmation prompts')
112
+ .option('--file <path>', 'Read credential from file (for multiline content)')
113
+ .option('--stdin', 'Read credential from stdin')
114
+ .action((subcommand, args, options) => {
115
+ keychainCommand(subcommand, args, options);
116
+ });
117
+
106
118
  // Error handling
107
119
  program.exitOverride();
108
120
 
@@ -133,6 +133,10 @@ async function initCommand(options) {
133
133
  name: 'hostname',
134
134
  message: 'Hostname',
135
135
  initial: (prev, values) => {
136
+ // For sandbox, use just username.local; for other types include project name
137
+ if (values.projectType === 'sandbox') {
138
+ return `${userName}.local`;
139
+ }
136
140
  return `${userName}-${values.projectName}.local`;
137
141
  },
138
142
  validate: value => {