Skip to content
Open
Show file tree
Hide file tree
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
61 changes: 61 additions & 0 deletions cmd/oap/internal/health/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package health

import (
"context"
"fmt"
"strings"

apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/authzed/openagentprimitives/cmd/oap/internal/kube"
"github.com/authzed/openagentprimitives/pkg/platform/bootstrap"
)

func init() {
// Verifies the memory-token Secrets exist. This is the GitOps-specific check:
// `oap install` creates these imperatively, but a bundle synced by Argo/Flux
// relies on the pre-start bootstrap Job (the operator image's
// `bootstrap-secrets` entrypoint) to mint them — so their absence is the
// signal that the Job never completed (wrong image, backoff exhausted, or the
// operator ServiceAccount is missing Secret RBAC).
Register(memoryTokenSecrets{})
}

// memoryTokenSecrets checks that each memory-token Secret exists and carries a
// non-empty token. It covers the three memory tokens (bootstrap.MemoryTokenSecretNames)
// the bootstrap Job mints; the other app-internal secrets the Job mints (admind,
// passthrough, NATS, spicedb/postgres) are verified indirectly by the workload
// health checks that mount them.
type memoryTokenSecrets struct{}

func (memoryTokenSecrets) Name() string { return "memory-token-secrets" }

func (memoryTokenSecrets) Check(ctx context.Context, b *kube.Bundle) Result {
var missing []string
present := 0
for _, name := range bootstrap.MemoryTokenSecretNames {
sec, err := b.Typed.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
switch {
case apierrors.IsNotFound(err):
missing = append(missing, name)
case err != nil:
return Result{Status: Failed, Detail: fmt.Sprintf("get %s: %v", name, err)}
case len(sec.Data[bootstrap.TokenSecretKey]) == 0:
missing = append(missing, name+" (empty token)")
default:
present++
}
}
total := len(bootstrap.MemoryTokenSecretNames)
switch {
case len(missing) == 0:
return Result{Status: OK, Detail: fmt.Sprintf("%d/%d memory tokens present", present, total)}
case present == 0:
// None present: the bootstrap Job has not minted them. NotFound lets a
// trimmed profile skip, matching the workload checks.
return Result{Status: Failed, Detail: "no memory tokens present (the bootstrap Job has not minted them)", NotFound: true}
default:
return Result{Status: Failed, Detail: "missing memory tokens: " + strings.Join(missing, ", ")}
}
}
70 changes: 70 additions & 0 deletions cmd/oap/internal/health/secrets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package health

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"

"github.com/authzed/openagentprimitives/cmd/oap/internal/kube"
"github.com/authzed/openagentprimitives/pkg/platform/bootstrap"
)

func tokenSecret(name string, token string) *corev1.Secret {
s := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}}
if token != "" {
s.Data = map[string][]byte{bootstrap.TokenSecretKey: []byte(token)}
}
return s
}

func bundleWith(objs ...runtime.Object) *kube.Bundle {
return &kube.Bundle{Typed: fake.NewSimpleClientset(objs...)}
}

func TestMemoryTokenSecrets_AllPresentOK(t *testing.T) {
var objs []runtime.Object
for _, n := range bootstrap.MemoryTokenSecretNames {
objs = append(objs, tokenSecret(n, "deadbeef"))
}
res := memoryTokenSecrets{}.Check(context.Background(), bundleWith(objs...))
assert.Equal(t, OK, res.Status)
assert.False(t, res.NotFound)
}

func TestMemoryTokenSecrets_MissingOneFails(t *testing.T) {
var objs []runtime.Object
for _, n := range bootstrap.MemoryTokenSecretNames[1:] { // omit the first
objs = append(objs, tokenSecret(n, "deadbeef"))
}
res := memoryTokenSecrets{}.Check(context.Background(), bundleWith(objs...))
assert.Equal(t, Failed, res.Status)
assert.Contains(t, res.Detail, bootstrap.MemoryTokenSecretNames[0], "names the missing Secret")
assert.False(t, res.NotFound, "some present → not a clean NotFound")
}

func TestMemoryTokenSecrets_EmptyTokenFails(t *testing.T) {
var objs []runtime.Object
objs = append(objs, tokenSecret(bootstrap.MemoryTokenSecretNames[0], "")) // present but empty
for _, n := range bootstrap.MemoryTokenSecretNames[1:] {
objs = append(objs, tokenSecret(n, "deadbeef"))
}
res := memoryTokenSecrets{}.Check(context.Background(), bundleWith(objs...))
assert.Equal(t, Failed, res.Status)
assert.Contains(t, res.Detail, "empty token")
}

func TestMemoryTokenSecrets_AllAbsentNotFound(t *testing.T) {
res := memoryTokenSecrets{}.Check(context.Background(), bundleWith())
assert.Equal(t, Failed, res.Status)
assert.True(t, res.NotFound, "none present → NotFound so trimmed profiles can skip")
// The tokens are minted by the pre-start bootstrap Job, NOT the operator
// runnable (that design was abandoned). A cold-start diagnosis must point at
// the Job so an operator doesn't hunt for operator mint logs that never exist.
assert.Contains(t, res.Detail, "bootstrap Job", "attributes a cold-start miss to the Job, not the operator")
assert.NotContains(t, res.Detail, "operator bootstrap", "must not misdirect to the abandoned operator-mint design")
}
34 changes: 7 additions & 27 deletions cmd/oap/internal/installcmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package installcmd
import (
"bufio"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"os"
Expand All @@ -14,9 +12,7 @@ import (

"github.com/spf13/cobra"
"golang.org/x/term"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -32,6 +28,7 @@ import (
"github.com/authzed/openagentprimitives/cmd/oap/internal/settingscmd"
"github.com/authzed/openagentprimitives/cmd/oap/internal/wait"
spiceboxv1alpha1 "github.com/authzed/openagentprimitives/pkg/apis/v1alpha1"
"github.com/authzed/openagentprimitives/pkg/platform/bootstrap"
"github.com/authzed/openagentprimitives/pkg/platform/cloud"
idpregistry "github.com/authzed/openagentprimitives/pkg/platform/identity/idp/registry"
"github.com/authzed/openagentprimitives/pkg/platform/manifests"
Expand Down Expand Up @@ -1102,27 +1099,10 @@ func ensureMemoryTokenSecret(ctx context.Context, restCfg *rest.Config, name str
if err != nil {
return err
}
var existing corev1.Secret
if err := cli.Get(ctx, types.NamespacedName{
Namespace: "agentprimitives-system",
Name: name,
}, &existing); err == nil {
return nil // already exists; idempotent
} else if !apierrors.IsNotFound(err) {
return fmt.Errorf("get %s secret: %w", name, err)
}

buf := make([]byte, 32)
if _, err := rand.Read(buf); err != nil {
return fmt.Errorf("generate token: %w", err)
}
sec := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "agentprimitives-system",
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{"token": []byte(hex.EncodeToString(buf))},
}
return cli.Create(ctx, sec)
// The mint logic lives in pkg/platform/bootstrap so the operator's leader-only
// bootstrap Runnable and this install fast path share ONE idempotent
// Create-if-absent path. A direct client.New client reads live (not a
// label-filtered cache), so it is safe as both reader and writer here.
_, err = bootstrap.EnsureTokenSecret(ctx, cli, cli, bootstrap.TokenSecretNamespace, name)
return err
}
Loading
Loading