Skip to content

chore: Add token rotation for grafana-operator service account#3745

Open
bengtfredh wants to merge 1 commit into
mainfrom
chore/update-grafana-module
Open

chore: Add token rotation for grafana-operator service account#3745
bengtfredh wants to merge 1 commit into
mainfrom
chore/update-grafana-module

Conversation

@bengtfredh

@bengtfredh bengtfredh commented Jun 22, 2026

Copy link
Copy Markdown
Member

Add automatic rotation of the grafana-operator service account token before it expires. Uses the time provider to trigger token regeneration every 180 days by default, preventing service disruption. Also upgrades the default Grafana version from 11 to 12.

Description

Related Issue(s)

  • #{issue number}

Verification

  • Your code builds clean without any errors or warnings
  • Manual testing done (required)
  • Relevant automated test added (if you find this hard, leave it and we'll help out)
  • All tests run green

Documentation

  • User documentation is updated with a separate linked PR in altinn-studio-docs. (if applicable)

Summary by CodeRabbit

Release Notes

  • New Features

    • Grafana operator service account tokens now automatically rotate based on configurable intervals (default: 180-day rotation with 360-day expiration) to prevent authentication failures.
  • Chores

    • Updated default Grafana major version to 12
    • Enhanced infrastructure module documentation

Add automatic rotation of the grafana-operator service account token
before it expires. Uses the time provider to trigger token regeneration
every 180 days by default, preventing service disruption. Also upgrades
the default Grafana version from 11 to 12.
@bengtfredh bengtfredh requested a review from a team as a code owner June 22, 2026 11:46
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Grafana Terraform module gains automatic rotation for the operator service account token. Two new variables (grafana_operator_token_expiration_days, grafana_operator_token_rotation_days) with a cross-field validation are added. A time_rotating resource drives periodic replacement of the token via lifecycle.replace_triggered_by, and seconds_to_live enforces the expiration TTL. The README is updated to reflect all changes.

Changes

Grafana Operator Token Auto-Rotation

Layer / File(s) Summary
Token rotation variables and resource wiring
infrastructure/modules/grafana/variables.tf, infrastructure/modules/grafana/grafana.tf
Adds grafana_operator_token_expiration_days (default 360) and grafana_operator_token_rotation_days (default 180) with a validation that rotation days must be strictly less than expiration days. Creates time_rotating.grafana_operator_token parameterized by the rotation variable, and updates grafana_service_account_token.grafana_operator to set seconds_to_live (expiration days × 86400) and a lifecycle.replace_triggered_by dependency on the rotating timer's id.
README documentation
infrastructure/modules/grafana/README.md
Adds the time provider to the Providers table, documents time_rotating.grafana_operator_token in the Resources table, updates grafana_major_version default to 12, and documents the two new token lifecycle input variables.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing automatic token rotation for the grafana-operator service account, which is the primary feature introduced across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/update-grafana-module

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@infrastructure/modules/grafana/grafana.tf`:
- Around line 59-61: The lifecycle block in the Grafana token rotation
configuration currently uses only replace_triggered_by, which enforces
destroy-before-create semantics that creates an authentication gap when the old
token is revoked before the new one is ready. Add create_before_destroy = true
to the lifecycle block alongside the existing replace_triggered_by configuration
to ensure the new token is fully created and propagated before the old token is
destroyed, eliminating the authentication window.

In `@infrastructure/modules/grafana/variables.tf`:
- Around line 87-103: The validation block for
grafana_operator_token_rotation_days only enforces ordering but does not
validate that both token expiration and rotation day values are positive whole
numbers. Add validation conditions to both
grafana_operator_token_expiration_days and grafana_operator_token_rotation_days
variables to ensure they are greater than zero and are not decimal values.
Update the existing validation block for grafana_operator_token_rotation_days to
include these additional conditions alongside the existing ordering check, and
add a new validation block to grafana_operator_token_expiration_days to enforce
the same positive whole-number constraints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f3c346e4-0410-44a4-98e3-dc7e8510871a

📥 Commits

Reviewing files that changed from the base of the PR and between 775a487 and 00d33a1.

📒 Files selected for processing (3)
  • infrastructure/modules/grafana/README.md
  • infrastructure/modules/grafana/grafana.tf
  • infrastructure/modules/grafana/variables.tf

Comment on lines +59 to +61
lifecycle {
replace_triggered_by = [time_rotating.grafana_operator_token.id]
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Terraform lifecycle behavior, when a resource is replaced due to replace_triggered_by, is the default destroy-before-create unless create_before_destroy = true` is set?

For the Grafana provider resource grafana_service_account_token, can multiple tokens with the same name exist simultaneously for one service account, or must names be unique?`

💡 Result:

In Terraform, when a resource is replaced due to replace_triggered_by, the default behavior is indeed to destroy the existing resource before creating the new one [1][2][3]. The replace_triggered_by attribute does not automatically enable create_before_destroy; if you require the replacement resource to be created before the old one is destroyed, you must explicitly set create_before_destroy = true within the resource's lifecycle block [2][3]. Note that create_before_destroy propagation can sometimes lead to dependency cycles, and in certain complex graph scenarios, Terraform may enforce or ignore specific lifecycle settings to maintain graph validity [4][1]. Regarding the Grafana provider resource grafana_service_account_token, multiple tokens with the same name can exist simultaneously for a single service account [5][6][7]. While the service account's own display name must be unique [5][7], there is no requirement for individual service account tokens associated with that account to have unique names; they are managed by their own unique internal IDs [5][7][8].

Citations:


🏁 Script executed:

cd infrastructure/modules/grafana && head -80 grafana.tf | tail -30

Repository: Altinn/altinn-platform

Length of output: 1183


🏁 Script executed:

cd infrastructure/modules/grafana && rg -A 5 -B 5 "grafana_operator" .

Repository: Altinn/altinn-platform

Length of output: 7108


Add create_before_destroy = true to prevent auth gaps during token rotation.

The current lifecycle using only replace_triggered_by enforces destroy-before-create semantics, which revokes the old token before the new token is created. This leaves a brief window where external consumers (like grafana-operator) using the rotated token would fail authentication. Since Grafana allows multiple tokens with the same name for a single service account, create_before_destroy = true can be safely added to ensure the new token is fully created and propagated to downstream consumers before the old token is revoked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infrastructure/modules/grafana/grafana.tf` around lines 59 - 61, The
lifecycle block in the Grafana token rotation configuration currently uses only
replace_triggered_by, which enforces destroy-before-create semantics that
creates an authentication gap when the old token is revoked before the new one
is ready. Add create_before_destroy = true to the lifecycle block alongside the
existing replace_triggered_by configuration to ensure the new token is fully
created and propagated before the old token is destroyed, eliminating the
authentication window.

Comment on lines +87 to +103
variable "grafana_operator_token_expiration_days" {
type = number
default = 360
description = "Lifetime in days for the grafana-operator service account token. Must be less than or equal to the Grafana instance's service_accounts.token_expiration_day_limit."
}

variable "grafana_operator_token_rotation_days" {
type = number
default = 180
description = "Number of days after which the grafana-operator service account token is rotated. Must be less than grafana_operator_token_expiration_days so the token is recreated before it expires."

validation {
condition = var.grafana_operator_token_rotation_days < var.grafana_operator_token_expiration_days
error_message = "grafana_operator_token_rotation_days must be less than grafana_operator_token_expiration_days so the token is rotated before it expires."
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add strict validation for positive whole-day values.

Line 99 only enforces ordering. Inputs like 0, negative numbers, or decimals still pass and can break rotation_days/seconds_to_live usage downstream.

Suggested patch
 variable "grafana_operator_token_expiration_days" {
   type        = number
   default     = 360
   description = "Lifetime in days for the grafana-operator service account token. Must be less than or equal to the Grafana instance's service_accounts.token_expiration_day_limit."
+
+  validation {
+    condition     = var.grafana_operator_token_expiration_days > 0 && floor(var.grafana_operator_token_expiration_days) == var.grafana_operator_token_expiration_days
+    error_message = "grafana_operator_token_expiration_days must be a positive whole number of days."
+  }
 }
 
 variable "grafana_operator_token_rotation_days" {
   type        = number
   default     = 180
   description = "Number of days after which the grafana-operator service account token is rotated. Must be less than grafana_operator_token_expiration_days so the token is recreated before it expires."
 
   validation {
-    condition     = var.grafana_operator_token_rotation_days < var.grafana_operator_token_expiration_days
-    error_message = "grafana_operator_token_rotation_days must be less than grafana_operator_token_expiration_days so the token is rotated before it expires."
+    condition     = var.grafana_operator_token_rotation_days > 0 && floor(var.grafana_operator_token_rotation_days) == var.grafana_operator_token_rotation_days && var.grafana_operator_token_rotation_days < var.grafana_operator_token_expiration_days
+    error_message = "grafana_operator_token_rotation_days must be a positive whole number less than grafana_operator_token_expiration_days."
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
variable "grafana_operator_token_expiration_days" {
type = number
default = 360
description = "Lifetime in days for the grafana-operator service account token. Must be less than or equal to the Grafana instance's service_accounts.token_expiration_day_limit."
}
variable "grafana_operator_token_rotation_days" {
type = number
default = 180
description = "Number of days after which the grafana-operator service account token is rotated. Must be less than grafana_operator_token_expiration_days so the token is recreated before it expires."
validation {
condition = var.grafana_operator_token_rotation_days < var.grafana_operator_token_expiration_days
error_message = "grafana_operator_token_rotation_days must be less than grafana_operator_token_expiration_days so the token is rotated before it expires."
}
}
variable "grafana_operator_token_expiration_days" {
type = number
default = 360
description = "Lifetime in days for the grafana-operator service account token. Must be less than or equal to the Grafana instance's service_accounts.token_expiration_day_limit."
validation {
condition = var.grafana_operator_token_expiration_days > 0 && floor(var.grafana_operator_token_expiration_days) == var.grafana_operator_token_expiration_days
error_message = "grafana_operator_token_expiration_days must be a positive whole number of days."
}
}
variable "grafana_operator_token_rotation_days" {
type = number
default = 180
description = "Number of days after which the grafana-operator service account token is rotated. Must be less than grafana_operator_token_expiration_days so the token is recreated before it expires."
validation {
condition = var.grafana_operator_token_rotation_days > 0 && floor(var.grafana_operator_token_rotation_days) == var.grafana_operator_token_rotation_days && var.grafana_operator_token_rotation_days < var.grafana_operator_token_expiration_days
error_message = "grafana_operator_token_rotation_days must be a positive whole number less than grafana_operator_token_expiration_days."
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@infrastructure/modules/grafana/variables.tf` around lines 87 - 103, The
validation block for grafana_operator_token_rotation_days only enforces ordering
but does not validate that both token expiration and rotation day values are
positive whole numbers. Add validation conditions to both
grafana_operator_token_expiration_days and grafana_operator_token_rotation_days
variables to ensure they are greater than zero and are not decimal values.
Update the existing validation block for grafana_operator_token_rotation_days to
include these additional conditions alongside the existing ordering check, and
add a new validation block to grafana_operator_token_expiration_days to enforce
the same positive whole-number constraints.

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.

2 participants