Skip to content

Release 17.2.16#1998

Merged
sk-keeper merged 5 commits intomasterfrom
release
Apr 28, 2026
Merged

Release 17.2.16#1998
sk-keeper merged 5 commits intomasterfrom
release

Conversation

@sk-keeper
Copy link
Copy Markdown
Collaborator

No description provided.

lthievenaz-keeper and others added 5 commits April 27, 2026 09:02
Running this:
`sf <folder> -e <user>`  yields this error:
` Folder.SharedFolderUpdateUser.manageRecords: Expected an int, got a boolean.`  

This is because the `SharedFolderUpdateUser` proto accepts `SetBooleanValue` as `manageRecords` and `manageUsers`, but that's not what we're setting conditionally.
This isn't a problem for  `SharedFolderUpdateTeam` because these accept `bool`.
This isn't a problem for  `SharedFolderUpdateRecord` because we make sure to only set `SetBooleanValue`
* PAM workflow: enforce allowedTimes window in launch gate

WorkflowAccessValidator now blocks launch when the current time is
outside config.parameters.allowedTimes (allowedDays / timeRanges in the
configured timeZone), matching the web vault behavior in
use-pam-workflow-preventing-tunnel-launch.ts. Handles overnight ranges
that cross midnight. Falls back to local time if zoneinfo is
unavailable (Python <3.9) or the IANA tz name is unknown.

* PAM workflow: hard-disconnect pam launch and pam tunnel at lease expiry

Mirrors web vault behavior: when the workflow lease expiresOn is
reached during an active session, the connection is torn down
immediately — no grace period, no warning, no reconnect attempt
(matches ConnectionManager.scheduleWorkflowAccessExpiry in vault).

Adds WorkflowGate (NamedTuple) and check_workflow_for_launch(),
threading flow_uid and expires_on_ms (millis since epoch) from the
active workflow back to the launch path. Old check_workflow_and_prompt_2fa
is kept as a thin shim for backward compatibility.

pam launch: a daemon threading.Timer fires _on_lease_expired() at
expiresOn, flipping shutdown_requested and lease_expired. The "Access
expired — session terminated" line is printed AFTER
reset_local_terminal_after_pam_session() so the message survives the
terminal reset.

pam tunnel start: a daemon threading.Timer calls
tube_registry.close_tube(tube_id, reason=Normal) at expiresOn.

* PAM workflow: prompt for reason/ticket inline at launch instead of bailing

When pam launch / pam tunnel start hits a workflow in WS_NEEDS_ACTION
with AC_REASON or AC_TICKET pending, Commander now collects the missing
fields inline (prompt_toolkit; multi-line for reason, single-line for
ticket) and submits the request directly, then re-validates so the user
sees the resulting state (waiting / ready_to_start / started). Previously
the user was told to run pam workflow request and re-launch.

New flags --reason / -r and --ticket / -tk on both pam launch and pam
tunnel start drive the same flow non-interactively. Pure-approval
workflows (AC_APPROVAL only) are also auto-submitted.

Refactor: extracted submit_access_request() and prompt_for_reason_ticket()
into workflow/helpers.py; WorkflowRequestAccessCommand now reuses
submit_access_request. WorkflowAccessValidator returns block_reason and
pending_conditions so the orchestrator can decide between printing the
"Run: pam workflow request..." hint and prompting inline.

* PAM workflow: prompt to check out when launch hits WS_READY_TO_START

When a workflow is approved but not yet checked out, pam launch and
pam tunnel start now offer to perform the start_workflow call inline
("Check out 'RECORD' now? [Y/n]") and re-validate, instead of bailing
with a "Run: pam workflow start..." hint. New --auto-checkout / -aco
flag confirms automatically for non-interactive runs.

The orchestrator runs validate() in a small transition loop (max 3:
needs_action -> ready_to_start -> started) so a workflow that has no
approval requirement and lands in ready_to_start right after submitting
a reason/ticket flows through without the user having to re-launch.

WorkflowGate now carries started_by_launch=True when the orchestrator
itself triggered the checkout. This is the input Phase 1.2 (auto check-in
on session end) needs to know whether the launch owns the lease.

Refactor: validate()'s auto_prompt_actionable parameter renamed to
silent_actionable, now suppresses both WS_NEEDS_ACTION and
WS_READY_TO_START prints. start_workflow_for_record() extracted to
workflow/helpers.py.

* PAM workflow: auto check-in pam launch session when launch owns the checkout

When pam launch itself triggered start_workflow (gate.started_by_launch=
True from Phase 2.2 auto-checkout), the lease is now released in the
session-end finally via end_workflow(flow_uid). Skipped when the lease
already expired (server-side release) or when the user pre-checked-out
manually via pam workflow start.

pam tunnel start does not yet auto check-in on stop — the tunnel session
is fire-and-forget so the gate info would need to be persisted on
TunnelSession and consumed by pam tunnel stop. Lease expiry already
tears down the tunnel via Phase 1.3's daemon timer, so the only gap is
manual "pam tunnel stop" without an explicit "pam workflow end" — left
as a follow-up.

* PAM workflow: --wait polls for approval before launching

When pam launch / pam tunnel start hits a workflow in WS_WAITING (request
submitted, awaiting approver action), the new --wait / -w flag polls
get_user_access_state every 8 seconds until the workflow transitions out
of waiting (approved -> ready_to_start or started, or denied -> back to
needs_action) or until --wait-timeout elapses (default 600 seconds).

Without --wait the existing immediate-exit behavior is preserved.
Ctrl+C cancels the wait cleanly.

The orchestrator's transition loop now allows the
needs_action -> waiting -> ready_to_start -> started chain end-to-end.
silent_actionable=True now also suppresses the WS_WAITING print so
polling iterations stay quiet; one-shot use prints via _print_waiting.

* PAM workflow: skip MFA prompt when gateway is offline

Match web vault LaunchButton.tsx:151-164 — when the controllerStatus is
not Online, the workflow MFA prompt is skipped (the launch proceeds
without two_factor_value and surfaces its own gateway-offline error
when it tries to talk to the router).

is_gateway_online_for_record(params, record_uid) is best-effort: it uses
launch_cache.get to avoid an expensive PAM_LINK DAG rebuild on the first
launch. On cache miss or any lookup failure it returns None, in which
case the orchestrator keeps the existing behavior (prompt for MFA).
Subsequent launches with a warm cache get the offline-skip behavior.

* PAM action rotate: enforce the same two gates as the web vault

Web vault rotation does not apply workflow gates (approval / checkout /
justification / MFA / time-window) — see
PasswordRotation.tsx:137-209 + dag-pam-link.ts:334. Rotation is treated
as a privileged maintenance operation. The only two checks WV applies
on the Rotate Now button are:

  1. getAllowRotation()           — enterprise enforcement
                                    (allow_rotate_credentials, with a
                                     legacy allow_pam_rotation fallback)
  2. getConfigAllowedSettings()   — per-PAM-config allowedSettings.rotation

This commit adds the analogs for pam action rotate:

  - _is_rotation_allowed_by_enforcement(params): checked once at the
    top of execute() so both single-record and folder-pattern paths are
    gated. Intentionally more permissive than WV's default-deny: only
    returns False when the enforcement explicitly sets
    allow_rotate_credentials=False (or legacy allow_pam_rotation=False).
    Personal / non-enterprise accounts where params.enforcements is None,
    empty, missing 'booleans', or carries unrelated grants are not
    blocked. The check is wrapped in try/except and tolerates any
    unexpected payload shape, so a malformed enforcements blob can't
    break rotation for non-enterprise users.

  - PAM config rotation switch: re-uses the existing
    PAMConfigurationListCommand._pam_config_allowed_settings_json
    helper to read allowedSettings from the PAM config DAG; when
    rotation is explicitly False on the config the rotation is skipped
    with a clear "disabled by PAM Configuration" message. The helper
    already swallows all DAG-load exceptions and returns rotation=None
    in that case; the call site adds a second try/except and uses
    `is False` so None values (no DAG entry, personal users, lookup
    failures) fall through to allow.

This replaces the earlier (incorrect) workflow gating attempt: the
--reason / --ticket / --auto-checkout / --wait / --wait-timeout flags
added previously have been removed since pam launch / pam tunnel start
remain the only places where workflow gates apply, matching WV.

* PAM tunnel: document that stop does not release the workflow lease

Adds an explanatory comment on PAMTunnelStopCommand and a symmetry note
at the workflow gate in PAMTunnelStartCommand. The behavior matches the
web vault (ConnectionManager.ts:268-303 stops the connection without
calling end_workflow); the workflow lease and the tunnel are decoupled
so a single approval window can host many sequential/concurrent tunnels.
The lease ends via expiresOn server-side or an explicit
`pam workflow end`. Releasing automatically on tunnel stop would clobber
leases the user established manually via `pam workflow start`, so the
non-release is intentional, not a gap.

* PAM launch / tunnel: gate on PAM config allowedSettings before lease auto-checkout

Mirrors web vault behavior — the Launch button checks
getConfigAllowedSettings(recordUid).connections
(GuacConnectBanner.tsx:37-45, message "launching_restricted_config")
and the Start Port-Forward button checks
getConfigAllowedSettings(recordUid).portForwards
(StartPortForwardButton.tsx:160-163, message "tunnel_disabled_config")
BEFORE any workflow gate runs. Order is:

  enforcement → PAM config → workflow

Adds is_pam_config_action_allowed_for_record(params, record_uid, action_key)
in workflow/helpers.py. Reuses
PAMConfigurationListCommand._pam_config_allowed_settings_json (which
already wraps DAG load in try/except). Fast path: read config_uid from
launch_cache when available; otherwise fall back to the full
get_config_uid_from_record DAG resolution.

Returns True (allow) on any lookup failure (personal accounts, no PAM
context, missing DAG, non-PAM record types) so non-enterprise usage is
not blocked. Returns False only when the flag is explicitly False on
the PAM config DAG.

pam launch passes action_key='connections'; pam tunnel start passes
action_key='tunneling' (the JSON helper renames the DAG key
portForwards → tunneling).

Closes a previously identified gap: auto-checkout would otherwise take
a workflow lease for a record where the PAM config disables the action,
wasting the lease on a launch/tunnel that would fail downstream.

* PAM launch / tunnel: per-user enforcement gate before PAM-config gate

Closes the last WV-parity gap. Web vault gates Connect / Start-Tunnel
buttons on a per-user enterprise enforcement boolean before any other
check (pam-enforcement-selectors.ts:38-49):

  getAllowConnections   → allow_launch_pam_on_cloud_connection
  getAllowPortForwards  → allow_launch_pam_tunnels

These are already the rolled-up sum of the user's role permissions —
verified against WV that no additional role / team / record-ACL /
deny-list checks gate the buttons. Order in WV is:

  enforcement → PAM config → workflow

Adds is_pam_action_allowed_by_enforcement(params, key) in
workflow/helpers.py and wires it into pam launch (key
'allow_launch_pam_on_cloud_connection') and pam tunnel start (key
'allow_launch_pam_tunnels'), called BEFORE the PAM-config gate.

Same defensive semantics as the rotation enforcement check
(_is_rotation_allowed_by_enforcement): allow by default, deny only on
explicit False; tolerates None / empty / malformed enforcement payloads
so personal / non-enterprise accounts are never blocked.

License gate (mkPam's isPamEnabled) intentionally skipped — Commander
has historically treated license defensively (let the gateway / server
fail with a specific error rather than pre-flighting client-side). Same
approach as the rotation enforcement check. Documented in detail in the
helper's docstring so future maintainers see the deviation and the
trade-off (granted enforcement + no license means the user passes this
gate and fails later at the gateway with a more specific error).

* PAM workflow: encode TimeOfDayRange as HHMM (server format), not minutes-since-midnight

* PAM workflow create: skip auto-add of creator as approver when approvalsNeeded=0

* PAM workflow create: drop creator auto-add; require --approver when approvalsNeeded > 0

* PAM workflow create: pre-check existing config and fail with an actionable message

* PAM launch: fall back to pam/get_configuration_controller when get_controllers misses (matches web vault)

* PAM tunnel: dedup lease-expiry timer per record, document soft-close limitation

* PAM workflow delete: pre-check existing config and bail with clear message when nothing to delete

* PAM workflow: handle no_workflow / needs_start with inline prompt + initial request submission (matches web vault first-time flow)

* Strict-deny PAM enforcement helpers when key absent in enterprise context

* Rotation enforcement: drop legacy allow_pam_rotation fallback to honor allow_rotate_credentials:false

* PAM launch: drop redundant catch-all error after workflow gate, mirror tunnel

* Workflow gate: allow on transport error so prod routers without workflow API don't hard-block legacy launch/tunnel
@sk-keeper sk-keeper merged commit 6574827 into master Apr 28, 2026
5 checks passed
Comment on lines +181 to +182
print(f"Run: pam workflow add-approver {record_uid} "
f"{' '.join(f'--user {u}' for u in approvers)}")

def _print_waiting(self, conditions, checked_out_by: str = ''):
cond_str = WorkflowFormatter.format_conditions(conditions) if conditions else 'approval'
print(f"\n{bcolors.WARNING}Workflow access is pending: waiting for {cond_str}.{bcolors.ENDC}")
f"to request approval.")
else:
cond_str = WorkflowFormatter.format_conditions(conditions)
print(f"Pending conditions: {cond_str}")
Comment on lines +327 to +328
print(f"Run: {bcolors.OKBLUE}pam workflow state --flow-uid {flow_uid_str}{bcolors.ENDC} "
f"to see details.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants