google / google/stellar-engine

[Bug] gemini-enterprise time-based access: the configured time zone is not honored — a re-run silently resets it to America/New_York, and the level's title and hour descriptions assert Eastern regardless of what was set

Open
#187 0 comments 0 reactions 0 assignees View on GitHub
bug gemini for government Level of Effort - Low Priority - Medium
Dominant language
HCL
Stars
49
Forks
20
Avg merge
3d 14h
Merged PRs (30d)
28

Description

## Bug Description

Three defects in the same feature, all of which make a non-Eastern deployment look and behave as Eastern. They share ten lines of code, so they are reported together; splitting them is fine if preferred.

### 1. The hour variables are documented as Eastern, unconditionally

```hcl
# gemini-stage-0/variables.tf:102-110
variable "access_start_hour" {
description = "The hour (0-23) in America/New_York timezone when access starts."
}
variable "access_end_hour" {
description = "The hour (0-23) in America/New_York timezone when access ends."
}
```

Both descriptions name `America/New_York` as the timezone the hour is expressed in. That is only true when `access_time_zone` is left at its default. The expression actually evaluates the hour in whatever `access_time_zone` is set to (`access_policy.tf:51` — `request.time.getHours("${var.access_time_zone}")`), so for any other timezone the documentation is simply wrong about what the number means. An operator configuring `America/Anchorage` reads that `access_start_hour = 8` is 8am Eastern; it is 8am Alaska.

### 2. The access level's title hardcodes Eastern

```hcl
# gemini-stage-0/access_policy.tf:48
title = "Business Hours East Coast"
```

This never varies with `access_time_zone`. Anyone reading the level in the Console or via `gcloud access-context-manager levels describe time` sees "Business Hours East Coast" asserted over a window that may be Alaska, Pacific or anything else. Combined with defect 1, every user-facing surface of this feature claims Eastern while the evaluation is doing something different — which is precisely the state that makes a misconfiguration invisible.

**To be clear about what is NOT broken:** the CEL expression itself is correct. It substitutes `var.access_time_zone` into all four `getHours` / `getDayOfWeek` calls, so a level built from a fresh, complete run *evaluates* in the configured zone. The bug is that the labels and docs say otherwise — and defect 3 then throws the configured value away entirely.

### 3. "Preserving" silently overwrites the configured window with the defaults
When the `time` access level already exists and is Terraform-managed, `deploy.sh` reports that it is preserving it and skips every time-related prompt:

```bash
# deploy.sh:1143-1150
if echo "$EXISTING_LEVELS" | grep -qE "(/|^)time$"; then
if [[ "$MANAGED_ACCESS_LEVELS" == *"time"* ]]; then
echo -e "${GREEN}Found existing MANAGED Access Level 'time'. Preserving.${NC}"
CREATE_TIME_ACCESS="true"
else
echo -e "${YELLOW}Access Level 'time' already exists (Unmanaged). Skipping.${NC}"
CREATE_TIME_ACCESS="false"
fi
else
# the five prompts — start day, end day, start hour, end hour, TIME ZONE — live here
fi
```

Because the prompts are skipped, `ACCESS_START_DAY`, `ACCESS_END_DAY`, `ACCESS_START_HOUR`, `ACCESS_END_HOUR` and `ACCESS_TIME_ZONE` are all unset. They are never hydrated from prior state either — the only assignments to any of them in the whole script are the prompts at `:1155-1164`.

The tfvars writer then skips each one, because every write is guarded on the variable being non-empty:

```bash
# deploy.sh:2455-2469
if [[ -n "$ACCESS_START_DAY" ]]; then echo "access_start_day = ${ACCESS_START_DAY}" >> gemini-stage-0/terraform.tfvars; fi
...
if [[ -n "$ACCESS_TIME_ZONE" ]]; then echo "access_time_zone = \"${ACCESS_TIME_ZONE}\"" >> gemini-stage-0/terraform.tfvars; fi
```

So nothing is written, and Terraform falls back to the variable defaults — `access_time_zone = "America/New_York"`, `access_start_hour = 7`, `access_end_hour = 21`, `access_start_day = 1`, `access_end_day = 5`.

Meanwhile `CREATE_TIME_ACCESS="true"` means the resource is still fully managed, and its expression is rebuilt from exactly those variables:

```hcl
# gemini-stage-0/access_policy.tf:44-53
resource "google_access_context_manager_access_level" "time" {
count = var.access_policy_number != "" && var.create_time_access ? 1 : 0
title = "Business Hours East Coast"
custom { expr {
expression = ("request.time.getHours(\"${var.access_time_zone}\") >= ${var.access_start_hour} && ... <= ${var.access_end_day}")
}}
}
```

**The apply therefore overwrites the operator's configured window with the East-Coast defaults, immediately after printing "Preserving."** The script reports the opposite of what it does.

**The `expire` level has the identical defect** (`deploy.sh:1173-1180`): "Preserving" + `CREATE_EXPIRE_ACCESS="true"`, no prompt, `ACCESS_EXPIRATION_TIMESTAMP` unset, not written, so the expiry silently resets to the default `2028-01-01T00:00:00Z`. The `us` level shares the shape but carries no parameters, so nothing is lost there.

## Environment and Deployment Context
* **Stellar Engine Version/Commit:** `main` at commit `f64ce6cd` (= tag `v3.0.0`), verified 2026-08-11
* **Deployment Type:**
* [ ] US Region Restricted (e.g., Access Policy constraint)
* [ ] FedRAMP Medium
* [x] FedRAMP High
* [ ] FedRAMP Moderate
* [ ] DoD IL4
* [ ] DoD IL5
* [ ] Stand-alone / Custom
* **FAST Stage (if applicable):** N/A — this is a blueprint, not a FAST stage
* [ ] Stage 0 (Bootstrap)
* [ ] Stage 1 (Resource Management)
* [ ] Stage 2 (Network Creation)
* [ ] Stage 3 (Security and Audit)
* **Affected Component:**
* `blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/variables.tf:102-110` (hour descriptions hardcoding `America/New_York`)
* `blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/access_policy.tf:48` (title hardcoding `Business Hours East Coast`)
* `blueprints/fedramp-high/gemini-enterprise/deploy.sh:1143-1150` (the "Preserving" branch that skips the prompts)
* `blueprints/fedramp-high/gemini-enterprise/deploy.sh:1155-1164` (the only assignments to the five variables)
* `blueprints/fedramp-high/gemini-enterprise/deploy.sh:2455-2469` (writes guarded on non-empty)
* `blueprints/fedramp-high/gemini-enterprise/deploy.sh:1173-1180` (same defect for `expire`)
* `blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/access_policy.tf:44-53` (the managed resource)
* **Terraform Version:** `1.12.2` (pinned by `deploy.sh` via tfenv; the stage declares `required_version >= 1.7.4`)
* **GCP Provider Version:** `hashicorp/google >= 6.21.0` (stage-0 declared constraint)

## Steps to Reproduce
1. Run `deploy.sh`, answer **y** to time-based access, and set a non-default window — e.g. Time Zone `America/Anchorage`, start hour `8`, end hour `18`.
2. Complete the apply. Read the level back and confirm the expression carries `America/Anchorage` and `>= 8 && <= 18`.
3. Run `deploy.sh` again against the same environment — any second pass, which is the normal path for adding an application or re-running a stage.
4. Observe `Found existing MANAGED Access Level 'time'. Preserving.` and note that no time prompts appear.
5. Complete the apply and read the level again: `gcloud access-context-manager levels describe time --policy `.

## Expected Behavior
The configured time zone is honored and represented consistently. Specifically: the hour descriptions say the hour is expressed in `access_time_zone`, not in `America/New_York`; the level's title reflects the configured zone rather than asserting Eastern; and "Preserving" preserves — either the existing values are read back and re-written unchanged, or the operator is re-prompted with them as defaults, or the resource is left unmanaged so Terraform does not touch it.

## Actual Behavior
Every user-facing surface asserts Eastern regardless of configuration, and the configured value does not survive a second run:

* `access_start_hour` / `access_end_hour` are documented as being "in America/New_York timezone" whatever `access_time_zone` is set to.
* The level is titled `Business Hours East Coast` whatever `access_time_zone` is set to.
* On any re-run through the wizard, the level is rewritten to the defaults: timezone reverts to `America/New_York`, hours to `07:00-21:00`, days to Mon-Fri. For a deployment configured in `America/Anchorage` that shifts the effective window by four hours — an `08:00-18:00` Alaska window silently becomes `07:00-21:00` Eastern, i.e. `03:00-17:00` local. No warning, no prompt, and the console line says the opposite.

The net effect is that an operator who sets a non-Eastern zone has no surface anywhere — variable docs, level title, or a second wizard run — that agrees with what they configured.

## Relevant Logs and Errors
No error is emitted — that is the defect. The only operator-visible signal is the line that misdescribes what happens:

```
--- Time Based Access ---
Found existing MANAGED Access Level 'time'. Preserving.
```

The resulting expression, rebuilt from the defaults:

```
request.time.getHours("America/New_York") >= 7 && request.time.getHours("America/New_York") <= 21 &&
request.time.getDayOfWeek("America/New_York") >= 1 && request.time.getDayOfWeek("America/New_York") <= 5
```

## Suggested Fix
**Defects 1 and 2 are one-liners.** Change the `access_start_hour` / `access_end_hour` descriptions to say the hour is expressed in `access_time_zone` (naming `America/New_York` only as the default), and make the level title carry the configured zone — e.g. `"Business Hours (${var.access_time_zone})"` — instead of hardcoding East Coast.

**Defect 3**, in the "Preserving" branch, do one of:
1. **Read the existing level back** and populate `ACCESS_*` from it before the tfvars write, so the values round-trip; or
2. **Re-prompt** using the existing values as the defaults; or
3. **Set `CREATE_TIME_ACCESS="false"`** so Terraform stops managing a level the script has decided not to reconfigure — matching what "Preserving" already implies.

Whichever is chosen, the console message must match the behavior, and the same fix applies to the `expire` branch.

## Additional Context
Found while running a deployment configured for `America/Anchorage`. Defect 3 is security-relevant rather than cosmetic: a time-based access **control** silently shifts its effective window on an ordinary re-run, and an operator who reads the "Preserving" line has no reason to re-check it. Defects 1 and 2 are what make it hard to catch — every label and description the operator can consult says Eastern, so the reset back to Eastern looks like the intended state.

Related: [#186](https://github.com/google/stellar-engine/issues/186) covers the day-numbering mismatch in the same expression — the day prompts at `deploy.sh:1155` / `:1157` and the `access_start_day` / `access_end_day` descriptions all state `1=Mon, 7=Sun` while CEL's `getDayOfWeek()` is 0-6. Between the two reports, every input to this one access level is either mis-documented or discarded on re-run; they are worth fixing together.

Contributor guide

Open the contributing guide

Research direction

Start with deploy.sh:1143-1180 and 2455-2469 to trace the preserving branches and tfvars writes, then inspect gemini-stage-0/variables.tf:102-110 and access_policy.tf:44-53. Re-run the deployment and use the issue's gcloud access-context-manager command to compare the configured values before and after. Done means the configured timezone and window survive re-runs, labels describe them accurately, and the expire branch no longer resets its value.

Written by the indexing model from the issue text.

Assessment

Tech stack
shell, terraform
Domain
cloud, infrastructure, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.