Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6c8fe5a89 | |||
| b1cfaa1046 | |||
| 2f0e291b29 | |||
| db2db5095b | |||
| 79f1f62318 | |||
| d8717fd613 | |||
| f8a213a3bf | |||
| f90d319eeb |
@@ -2,7 +2,9 @@ name: Lint
|
|||||||
|
|
||||||
# theta-env has no app code of its own to unit-test (it orchestrates the
|
# theta-env has no app code of its own to unit-test (it orchestrates the
|
||||||
# proxy/sso-manager-node submodules) -- this checks the one thing that can
|
# proxy/sso-manager-node submodules) -- this checks the one thing that can
|
||||||
# actually break silently: setup.sh and bootstrap.js.
|
# actually break silently: setup.sh and bootstrap.js, plus a static
|
||||||
|
# consistency check on the config bootstrap.js generates for jump-host
|
||||||
|
# (test/check_jump_ldap_tls.js).
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
@@ -39,3 +41,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Syntax check
|
- name: Syntax check
|
||||||
run: node --check bootstrap/bootstrap.js
|
run: node --check bootstrap/bootstrap.js
|
||||||
|
|
||||||
|
- name: Jump-host LDAP config consistency
|
||||||
|
run: node test/check_jump_ldap_tls.js
|
||||||
|
|||||||
@@ -10,6 +10,29 @@ for what changed inside the apps it composes.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.11.0] - 2026-07-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **`test/check_jump_ldap_tls.js`**, wired into the `Lint` workflow: a static consistency check on the jump-secrets.js template `bootstrap.js` generates, so the `ldap://` + `tlsOptions` mistake that broke every SSH login in 1.10.0 fails CI before it ever reaches a real deployment again.
|
||||||
|
- **A static "no native `alert()`/`confirm()`/`prompt()`" check** is now part of all three apps' own test suites (they block all further browser events on the page — see 1.9.0/1.10.0's release notes).
|
||||||
|
|
||||||
|
### Bumped
|
||||||
|
- sso-manager-node -> [v1.6.2](https://github.com/theta42/sso-manager-node/releases/tag/v1.6.2) — fixes `DELETE /api/oauth/client/:id` (`client.remove is not a function`, a genuine 500 masked by tests that never checked the response status), plus the regression test above.
|
||||||
|
- proxy -> [v1.5.2](https://github.com/theta42/proxy/releases/tag/v1.5.2) — the regression test above.
|
||||||
|
- jump-host -> [v1.7.1](https://github.com/theta42/jump-host/releases/tag/v1.7.1) — the regression test above.
|
||||||
|
|
||||||
|
No `setup.sh` or compose change.
|
||||||
|
|
||||||
|
## [1.10.0] - 2026-07-27
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **`bootstrap/bootstrap.js`'s jump-secrets.js template now points jump-host at `ldaps://sso-manager:636`**, not `ldap://sso-manager:389`. The plain-port URL combined with jump-host's `tlsOptions` made `ldapts` attempt implicit TLS against a port serving plaintext LDAP — slapd dropped every connection before any LDAP message parsed, so SSH password login failed for every account, with any password, indistinguishable from a wrong credential. Root-caused by standing up a local jump-host, editing its config, and calling `getUser`/`checkPassword` directly inside the container. **Existing deployments must edit `./config/jump-secrets.js` themselves** (this template only affects fresh bootstraps) — see theta42/theta-env#99. Companion defensive fix: [simpleworkjs/ldap v1.0.2](https://github.com/simpleworkjs/ldap/releases/tag/v1.0.2) now rejects this `ldap://` + `tlsOptions` combination outright.
|
||||||
|
|
||||||
|
### Bumped
|
||||||
|
- jump-host -> [v1.7.0](https://github.com/theta42/jump-host/releases/tag/v1.7.0) — adds self-service API tokens (create/list/rotate/revoke from its dashboard); jump-host previously had none.
|
||||||
|
|
||||||
|
No `setup.sh` or compose change.
|
||||||
|
|
||||||
## [1.9.0] - 2026-07-27
|
## [1.9.0] - 2026-07-27
|
||||||
|
|
||||||
### Bumped
|
### Bumped
|
||||||
|
|||||||
Vendored
+10
-1
@@ -527,7 +527,16 @@ function writeJumpSecrets(apiToken, oidc, localAdminPass) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
\tname: ${JSON.stringify(sso.name || 'SSO Manager')},
|
\tname: ${JSON.stringify(sso.name || 'SSO Manager')},
|
||||||
\tldap: {
|
\tldap: {
|
||||||
\t\turl: 'ldap://sso-manager:389',
|
\t\t// ldaps:// (636), not ldap:// (389): @simpleworkjs/ldap's client always
|
||||||
|
\t\t// sets tlsOptions (see jump-host's models/user_ldap.js), and ldapts
|
||||||
|
\t\t// treats a non-empty tlsOptions as "use implicit TLS" regardless of the
|
||||||
|
\t\t// URL scheme -- pointed at the plain port, that means it opens a raw TLS
|
||||||
|
\t\t// handshake against a server expecting plaintext LDAP, which slapd just
|
||||||
|
\t\t// drops (logged as "connection lost", no BIND ever attempted). This bit
|
||||||
|
\t\t// jump-host silently: every SSH login failed with the generic
|
||||||
|
\t\t// "Permission denied" for any password, because getUser()/checkPassword()
|
||||||
|
\t\t// never even reached slapd.
|
||||||
|
\t\turl: 'ldaps://sso-manager:636',
|
||||||
\t\tbindDN: ${JSON.stringify(BIND_DN)},
|
\t\tbindDN: ${JSON.stringify(BIND_DN)},
|
||||||
\t\tbindPassword: ${JSON.stringify(ADMIN_PASS)},
|
\t\tbindPassword: ${JSON.stringify(ADMIN_PASS)},
|
||||||
\t\tuserBase: ${JSON.stringify(`ou=people,${BASE_DN}`)},
|
\t\tuserBase: ${JSON.stringify(`ou=people,${BASE_DN}`)},
|
||||||
|
|||||||
+1
-1
Submodule jump-host updated: 4a70b5b27e...0af2fc7e3d
+1
-1
Submodule proxy updated: 58204da3cb...001fe84b99
+1
-1
Submodule sso-manager-node updated: 18119d54aa...fcba782ac7
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Regression guard for bootstrap.js's generated jump-secrets.js template:
|
||||||
|
// its ldap block must use ldaps:// (implicit TLS, :636), never ldap:// (:389),
|
||||||
|
// as long as tlsOptions is set alongside it.
|
||||||
|
//
|
||||||
|
// ldapts treats a non-empty tlsOptions as "use implicit TLS" regardless of URL
|
||||||
|
// scheme, and jump-host's LDAP client always sets tlsOptions -- so ldap://
|
||||||
|
// + tlsOptions opens a raw TLS handshake against a port serving plaintext
|
||||||
|
// LDAP. The server silently drops the connection before any LDAP message
|
||||||
|
// parses, and every operation (getUser, checkPassword, ...) then fails
|
||||||
|
// identically -- indistinguishable from a wrong password. This shipped once
|
||||||
|
// (every SSH login to jump-host failed, for any account, any password) before
|
||||||
|
// being root-caused against a real deployment. Static, not a require()+exec
|
||||||
|
// of bootstrap.js, because bootstrap.js is a self-running provisioning script
|
||||||
|
// with real side effects (LDAP writes, API calls), not a library.
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const BOOTSTRAP_PATH = path.join(__dirname, '..', 'bootstrap', 'bootstrap.js');
|
||||||
|
const src = fs.readFileSync(BOOTSTRAP_PATH, 'utf8');
|
||||||
|
|
||||||
|
// Isolate the generated jump-secrets.js template (the backtick string
|
||||||
|
// assigned to `body` inside writeJumpSecrets) rather than scanning the whole
|
||||||
|
// file, so this only ever looks at what's actually written to the deployed
|
||||||
|
// config -- not, say, a comment or an unrelated ldap:// URL elsewhere.
|
||||||
|
// bootstrap.js's own source has literal backslash-t escape sequences inside
|
||||||
|
// the backtick string (they only become real tabs when the template
|
||||||
|
// literal is actually evaluated) -- so these patterns match `\t` as two
|
||||||
|
// literal characters, not a real tab byte.
|
||||||
|
const bodyMatch = /const body = `([\s\S]*?)`;\n\tfs\.writeFileSync\(JUMP_SECRETS/.exec(src);
|
||||||
|
if (!bodyMatch) {
|
||||||
|
console.error('check_jump_ldap_tls: could not locate the jump-secrets.js template in bootstrap.js — did writeJumpSecrets change shape?');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const template = bodyMatch[1];
|
||||||
|
|
||||||
|
// Bounded by the next top-level key (sso:) rather than the ldap block's own
|
||||||
|
// closing brace, which is more robust to exactly how it's indented/escaped.
|
||||||
|
const ldapBlockMatch = /ldap:\s*\{([\s\S]*?)\\tsso:\s*\{/.exec(template);
|
||||||
|
if (!ldapBlockMatch) {
|
||||||
|
console.error('check_jump_ldap_tls: could not find the ldap: {...} block in the jump-secrets.js template.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const ldapBlock = ldapBlockMatch[1];
|
||||||
|
|
||||||
|
const hasTlsOptions = /tlsOptions\s*:/.test(ldapBlock);
|
||||||
|
const urlMatch = /url:\s*'([^']+)'/.exec(ldapBlock);
|
||||||
|
const url = urlMatch ? urlMatch[1] : null;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
console.error('check_jump_ldap_tls: no url found in the ldap block.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTlsOptions && !url.startsWith('ldaps://')) {
|
||||||
|
console.error(
|
||||||
|
`check_jump_ldap_tls: jump-secrets.js template sets tlsOptions but url is "${url}" (not ldaps://). ` +
|
||||||
|
'This is the exact bug that broke every SSH login to jump-host -- see the comment above this check.'
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`check_jump_ldap_tls: OK (url=${url}, tlsOptions=${hasTlsOptions})`);
|
||||||
Reference in New Issue
Block a user