Skip to content

Release prep: v1 API, security fixes, ApiKey permission enforcement, README rewrite - #16

Merged
wasabii merged 7 commits into
mainfrom
claude/project-release-readiness-8ea870
Oct 18, 2026
Merged

wasabii merged 7 commits into
mainfrom
claude/project-release-readiness-8ea870

Conversation

@wasabii

@wasabii wasabii commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

API version

  • v1 is introduced as the storage version. Every kind is served at seq.k8s.datalust.co/v1 with exactly the v1alpha1 schema. v1alpha1 stays served but deprecated, so kubectl warns whenever it's used.
  • No conversion webhook is needed. Because the schemas are identical, the CRDs use conversion: None: the API server converts by relabelling apiVersion, and existing objects carry over untouched.
  • The operator works in v1 throughout. The V1alpha1* classes are renamed to V1*. The legacy finalizer shims keep their names, since their names are the finalizer identifiers. New tests check that the two versions can't drift apart.

Security

  • ApiKey permission flags are enforced. Before, any namespace allowed to create ApiKeys could assign itself System or Organization. Defaults now follow sensitivity: Ingest and Public are allowed unless denied, and everything else must be granted. A key asking for too much is refused whole, and its existing key in Seq is left untouched.
  • Secret references to other namespaces are refused. These are remote.auth on Instances and secretRef on ApiKeys. Before, the operator followed them with its cluster-wide access.
  • SSO secrets are kept out of Instance status. Status shows <set> instead of the value. Secrets can now also come from a Secret via clientSecretRef / clientKeyRef; the plain-text fields are deprecated.
  • Admin passwords are generated with RandomNumberGenerator. They previously used Random.Shared, which isn't a secure source.

Bugs

  • deployment.envFrom no longer crashes every reconcile.
  • Signals are in the default Instance permissions. Before, a Signal was refused even in the Instance's own namespace unless spec.permissions was set.
  • A failed create no longer leaves a duplicate in Seq. If the operator creates an object in Seq but can't record its id, it now removes the new object before retrying.
  • Deletions no longer hang when the Instance is gone or being deleted. They used to block deletion of the resource, and of any namespace containing it, forever.
  • The operator recognises its own ApiKey (<instance>-operator).
  • The StatefulSet's serviceName names the Service instead of the ServiceAccount. This only applies to new StatefulSets, because the field is immutable.

Docs

  • The README is rewritten, with examples checked against the generated CRDs, and has an Upgrading section for each behaviour change.

Upgrade impact

The README's Upgrading section covers each change. On the labs cluster nothing needs to change:

  • The cross-namespace ApiKeys there ask for Ingest only.
  • None of them reference a Secret in another namespace.
  • No SSO is configured.

Testing

  • The full dist build passes. It regenerates the CRDs, which come out with v1 stored, v1alpha1 served and deprecated, identical schemas, and conversion: None. It also renders the chart.
  • 33/33 tests pass.
  • A server-side dry run of the new CRDs against the labs cluster is accepted. That cluster currently stores v1alpha1 only.
  • Not deployed to a cluster.

…Set serviceName

The README is rewritten around what the operator actually does: installing, each resource with
examples checked against the generated CRDs, the namespace permission model, deletion, known
limitations, building, and releasing with GitVersion. The install command now notes that Helm
skips the prerelease builds main publishes.

Deployed Instances generated their admin and first-run passwords with Random.Shared, which is
not a secure source, and always put a special character first. They now come from
RandomNumberGenerator, drawn uniformly and guaranteed one character of each class.

The StatefulSet's serviceName named the ServiceAccount rather than the Service. It is now set to
the Service on creation only, since the field is immutable; existing StatefulSets keep theirs.
The apiKeys.set* flags in an Instance's spec.permissions were accepted but never checked, so any
namespace allowed to create ApiKeys could assign itself System or Organization. They are now
enforced, with defaults by sensitivity: Ingest and Public only send events and are allowed unless
denied; Read, Write, Project, System and Organization must be granted. An ApiKey asking for more
than it may is refused whole, with the reason in its status, and its key in Seq is left as it was.

The operator's own key was never recognised as such: it is named <instance>-operator, but the
check looked for <instance>. It now matches, so that key bypasses these checks as intended.
@wasabii wasabii changed the title Rewrite the README; fix admin password generation and StatefulSet serviceName Release prep: README rewrite, secure admin passwords, ApiKey permission enforcement Oct 18, 2026
…Secrets

The OIDC client secret and Entra client key were read back from Seq into status.info.auth, where
anyone with the view role could read them. Status now shows "<set>" in their place. Nothing reads
status back: changes are detected against what Seq returns, so redacting does not cause the
settings to be rewritten each reconcile.

The secrets can now be given as clientSecretRef / clientKeyRef, a key of a Secret in the
Instance's namespace, so they need not appear in the spec either. The plain-text fields still work
and are documented as deprecated; setting both forms is refused.
@wasabii wasabii changed the title Release prep: README rewrite, secure admin passwords, ApiKey permission enforcement Release prep: README rewrite, secure secrets and passwords, ApiKey permission enforcement Oct 18, 2026
…etions and cross-namespace Secrets

- deployment.envFrom threw on every reconcile: the container's EnvFrom list was never created.
- The default Instance permissions omitted Signals, so a Signal in the Instance's own namespace was
  refused unless spec.permissions was set.
- If recording a newly created object's ID failed (the status write, or an ApiKey's token
  Secret), the retry created it again, since most kinds cannot be found in Seq without the ID.
  The new object is now removed before the error surfaces.
- Deleting an entity whose Instance was gone, or being deleted and unreachable, held its
  finalizer forever, and with it any namespace being deleted. It is now released, leaving the
  object in Seq; nothing could reach Seq to remove it without the Instance anyway.
- Secret references naming another namespace were followed with the operator's cluster-wide
  access, letting anyone able to create an Instance read any Secret through remote.auth. They
  are now refused.
…ecated

Every kind is now served at seq.k8s.datalust.co/v1 with exactly the v1alpha1 schema, and v1 is
the storage version. The v1alpha1 entity types share the v1 spec and status types and declare
nothing of their own, so the API server converts between the versions by relabelling apiVersion
(conversion: None) and no webhook is needed. The operator works in v1 throughout.

The chart marks every non-storage version deprecated, with a warning naming its replacement,
since KubeOps has no attribute for it. A server-side dry run of the resulting CRDs against a live
cluster still storing v1alpha1 is accepted.

Classes are renamed from V1alpha1* to V1*, except the legacy finalizer shims, whose class names
are their finalizer identifiers. V1alpha1Condition becomes V1EntityCondition, since V1Condition
is taken by the Kubernetes client.
@wasabii wasabii changed the title Release prep: README rewrite, secure secrets and passwords, ApiKey permission enforcement Release prep: v1 API, security fixes, ApiKey permission enforcement, README rewrite Oct 18, 2026
@wasabii
wasabii force-pushed the claude/project-release-readiness-8ea870 branch from d333cac to 389f80e Compare October 18, 2026 16:10
Every object the operator created before v1 (an Instance's StatefulSet, Service, ServiceAccount,
login Secret and operator ApiKey, and each ApiKey's token Secret) carries an owner reference
naming seq.k8s.datalust.co/v1alpha1. The Kubernetes client's IsOwnedBy compares apiVersion as
well as UID, so once the operator read its Instances as v1 it no longer recognised any of them:
a deployed Instance would fail every reconcile with a StatefulSet conflict.

Ownership is now decided by UID, as the garbage collector does, and owner references are
rewritten to v1 whenever the operator updates the object, so none outlive v1alpha1.
@wasabii
wasabii merged commit d4429aa into main Oct 18, 2026
5 checks passed
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.

1 participant