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
- 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
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