Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions terraform-reference/resources/service_account_api_key.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,22 @@ resource "kosli_service_account" "ci" {
privilege = "member"
}

# API key with the server-side maximum lifetime (365 days from creation)
# Omitting expires_at lets the server choose the expiry, which is the maximum
# it allows: 365 days from creation. No API key can be created that never
# expires.
resource "kosli_service_account_api_key" "ci_key" {
service_account_name = kosli_service_account.ci.name
description = "Production CI key"
}

# API key with an explicit expiry (RFC3339 timestamp)
# An API key with an explicit expiry (RFC3339 timestamp). It must be in the
# future and no more than 365 days out. The server silently shortens anything
# longer.
resource "kosli_service_account_api_key" "ci_key_expiring" {
service_account_name = kosli_service_account.ci.name
description = "Temporary CI key"
expires_at = "2026-12-31T00:00:00Z"
# Pick a date within 365 days of when you apply; update it as you rotate.
expires_at = "2027-01-01T00:00:00Z"
}

# The raw key is only available on creation and is sensitive
Expand All @@ -54,21 +59,16 @@ output "ci_api_key" {

## Expiry

Every API key expires. The server caps each key's lifetime at **365 days from creation** and enforces the cap regardless of the client.
The `expires_at` attribute is an RFC3339 timestamp, e.g. `2027-01-01T00:00:00Z` (offsets such as `+01:00` are accepted and normalized to UTC). The timestamp must not be in the past.

The `expires_at` attribute is an RFC3339 timestamp, e.g. `2026-12-31T00:00:00Z` (offsets such as `+01:00` are accepted and normalized to UTC). Behavior:

- **Omitted** → the server applies the maximum 365-day expiry.
- **Set within 365 days** → honored as-is.
- **Set beyond 365 days** → silently shortened to the 365-day cap. Terraform then reports the result as an inconsistent result after apply and marks the resource tainted so the next apply revokes and recreates it. Choose an `expires_at` within the 365-day window to avoid this.
- **In the past** → rejected server-side at apply time.

Keys that never expire can no longer be created.
<Warning>
Kosli caps the lifetime of every API key at **365 days**. An `expires_at` further out than that is silently shortened by the server, and Terraform then reports the mismatch as `Provider produced inconsistent result after apply`. Keys that never expire can no longer be created: omitting `expires_at` yields the maximum 365-day expiry rather than no expiry at all.
</Warning>
Comment on lines +64 to +66

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.

Improvement — the rewrite names the error but drops what happens after it. The replaced bullet told the reader the resource is marked tainted and the next apply revokes and recreates the key; without that, someone who hits Provider produced inconsistent result after apply doesn't know their key is about to be rotated out from under them. Also, "365 days" loses the anchor that the sibling pages carry ("365 days from creation" — administration/authentication/api_key_rotation.md:14, tutorials/rotating_api_keys.mdx:23).

Suggested change
<Warning>
Kosli caps the lifetime of every API key at **365 days**. An `expires_at` further out than that is silently shortened by the server, and Terraform then reports the mismatch as `Provider produced inconsistent result after apply`. Keys that never expire can no longer be created: omitting `expires_at` yields the maximum 365-day expiry rather than no expiry at all.
</Warning>
<Warning>
Kosli caps the lifetime of every API key at **365 days from creation**. An `expires_at` further out than that is silently shortened by the server, and Terraform then reports the mismatch as `Provider produced inconsistent result after apply` and marks the resource tainted — the next apply revokes the key and creates a new one. Keys that never expire can no longer be created: omitting `expires_at` yields the maximum 365-day expiry rather than no expiry at all.
</Warning>


To derive dates dynamically, use Terraform's built-in functions, e.g. `timeadd("2026-01-01T00:00:00Z", "8760h")`.
To derive dates from a fixed anchor, use Terraform's built-in functions, e.g. `timeadd("2027-01-01T00:00:00Z", "720h")`. Avoid deriving `expires_at` from `timestamp()`. It changes on every plan, and because `expires_at` forces replacement that would revoke and recreate the key on each apply.

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.

Improvement — the timestamp() warning is missing its main clause, so the reason it matters is hard to parse: "It changes on every plan, and because expires_at forces replacement that would revoke and recreate the key on each apply." The consequence sentence never resolves.

Suggested change
To derive dates from a fixed anchor, use Terraform's built-in functions, e.g. `timeadd("2027-01-01T00:00:00Z", "720h")`. Avoid deriving `expires_at` from `timestamp()`. It changes on every plan, and because `expires_at` forces replacement that would revoke and recreate the key on each apply.
To derive dates from a fixed anchor, use Terraform's built-in functions, e.g. `timeadd("2027-01-01T00:00:00Z", "720h")`. Don't derive `expires_at` from `timestamp()`: it changes on every plan, and because a change to `expires_at` forces replacement, every apply would revoke the key and mint a new one.

Since this page is agent-synced from kosli-dev/terraform-provider-kosli, the same wording should land upstream or the next sync will reintroduce it.


<Note>
All timestamps (`expires_at`, `created_at`, `last_used_at`) are RFC3339 UTC strings. `last_used_at` is null for a key that has never been used.
All timestamps (`expires_at`, `created_at`, `last_used_at`) are RFC3339 UTC strings. `last_used_at` is null for a key that has never been used. `expires_at` is null only for keys minted before the 365-day cap was introduced.

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.

Improvement — this reintroduces a null expires_at state that the previous commit on this page deliberately removed. eb4485e (#407) deleted the "null for a key that never expires" callout with the stated reason that the state "can no longer exist", verified against kosli-dev/server (src/model/api_key.py::clamp_expires_at, MAX_API_KEY_LIFETIME_DAYS = 365).

Two problems as written:

  1. Nothing else in the docs supports the claim that pre-cap keys with a null expiry still exist in Kosli — the expires_at schema entry (line 96) doesn't mention null, and client_reference/kosli_create_api-key.md:16 states flatly that every API key expires.
  2. If such keys do exist, the page gives a reader who imports one no guidance: does Terraform show permanent drift, and does setting expires_at on it force replacement (i.e. revoke a working key)?

Either confirm the legacy state against the provider's read path and say what a reader should do with one, or drop the sentence.

</Note>

## Import
Expand All @@ -93,7 +93,7 @@ Because the raw key value is only returned at creation time, the `key` attribute

### Optional

- `expires_at` (String) RFC3339 timestamp at which the key expires, e.g. `2026-12-31T00:00:00Z` (offsets allowed; whole seconds only). Must not be in the past (validated server-side at apply time) and must be no more than 365 days out: the server caps every key's lifetime and silently shortens a longer expiry, which Terraform then reports as an inconsistent result after apply. Omit to let the server apply the maximum 365-day expiry; keys that never expire can no longer be created. Changing this forces creation of a new key. Removing a previously set value from configuration leaves the existing expiry unchanged.
- `expires_at` (String) RFC3339 timestamp at which the key expires, e.g. `2027-01-01T00:00:00Z` (offsets allowed; whole seconds only). Must not be in the past (validated server-side at apply time) and must be no more than 365 days out: the server caps every key's lifetime and silently shortens a longer expiry, which Terraform then reports as an inconsistent result after apply. Omit to let the server apply the maximum 365-day expiry; keys that never expire can no longer be created. Changing this forces creation of a new key. Removing a previously set value from configuration leaves the existing expiry unchanged.

### Read-only

Expand Down
Loading