-
Notifications
You must be signed in to change notification settings - Fork 17
OLS-3024: Add compliance audit logging to agentic operator #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,26 +1,36 @@ | ||||||||||||||||||||||
| package main | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import ( | ||||||||||||||||||||||
| "context" | ||||||||||||||||||||||
| "flag" | ||||||||||||||||||||||
| "os" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Import auth plugins (Azure, GCP, OIDC, etc.) for local and hosted kubeconfigs. | ||||||||||||||||||||||
| _ "k8s.io/client-go/plugin/pkg/client/auth" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| "github.com/go-logr/logr" | ||||||||||||||||||||||
| consolev1 "github.com/openshift/api/console/v1" | ||||||||||||||||||||||
| openshiftv1 "github.com/openshift/api/operator/v1" | ||||||||||||||||||||||
| "go.opentelemetry.io/otel" | ||||||||||||||||||||||
| "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" | ||||||||||||||||||||||
| "go.opentelemetry.io/otel/propagation" | ||||||||||||||||||||||
| "go.opentelemetry.io/otel/sdk/resource" | ||||||||||||||||||||||
| sdktrace "go.opentelemetry.io/otel/sdk/trace" | ||||||||||||||||||||||
| semconv "go.opentelemetry.io/otel/semconv/v1.26.0" | ||||||||||||||||||||||
| corev1 "k8s.io/api/core/v1" | ||||||||||||||||||||||
| "k8s.io/apimachinery/pkg/runtime" | ||||||||||||||||||||||
| utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||||||||||||||||||||||
| clientgoscheme "k8s.io/client-go/kubernetes/scheme" | ||||||||||||||||||||||
| ctrl "sigs.k8s.io/controller-runtime" | ||||||||||||||||||||||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||||||||||||||||||||||
| "sigs.k8s.io/controller-runtime/pkg/client/config" | ||||||||||||||||||||||
| "sigs.k8s.io/controller-runtime/pkg/healthz" | ||||||||||||||||||||||
| "sigs.k8s.io/controller-runtime/pkg/log/zap" | ||||||||||||||||||||||
| metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| agenticv1alpha1 "github.com/openshift/lightspeed-agentic-operator/api/v1alpha1" | ||||||||||||||||||||||
| agenticcontroller "github.com/openshift/lightspeed-agentic-operator/controller" | ||||||||||||||||||||||
| "github.com/openshift/lightspeed-agentic-operator/controller/proposal" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| var scheme = runtime.NewScheme() | ||||||||||||||||||||||
|
|
@@ -87,12 +97,48 @@ func main() { | |||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Read audit config from AgenticOLSConfig (best-effort at startup). | ||||||||||||||||||||||
| // spec.audit.logging and spec.audit.otel are independent controls. | ||||||||||||||||||||||
| auditLogging := true | ||||||||||||||||||||||
| var otelEndpoint string | ||||||||||||||||||||||
| otelInsecure := true | ||||||||||||||||||||||
| directClient, err := client.New(cfg, client.Options{Scheme: scheme}) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| log.Error(err, "unable to create direct client for audit config") | ||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| var agenticConfig agenticv1alpha1.AgenticOLSConfig | ||||||||||||||||||||||
| if err := directClient.Get(context.Background(), client.ObjectKey{Name: "cluster"}, &agenticConfig); err == nil { | ||||||||||||||||||||||
| auditLogging = proposal.ResolveLoggingEnabled(agenticConfig.Spec.Audit) | ||||||||||||||||||||||
| otelEndpoint = proposal.ResolveOTELEndpoint(agenticConfig.Spec.Audit) | ||||||||||||||||||||||
| otelInsecure = proposal.ResolveOTELInsecure(agenticConfig.Spec.Audit) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Create audit logger when logging is enabled (nil when disabled). | ||||||||||||||||||||||
| var auditLogger *proposal.AuditLogger | ||||||||||||||||||||||
| if auditLogging { | ||||||||||||||||||||||
| auditLogger = proposal.NewAuditLogger() | ||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||
| // Sync stdout returns "invalid argument" on many platforms — benign. | ||||||||||||||||||||||
| _ = auditLogger.Sync() | ||||||||||||||||||||||
| }() | ||||||||||||||||||||||
|
Comment on lines
+121
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don’t suppress every audit logger sync error. Line 123 hides real flush failures along with the known stdout Suggested fix defer func() {
// Sync stdout returns "invalid argument" on many platforms — benign.
- _ = auditLogger.Sync()
+ if err := auditLogger.Sync(); err != nil && !errors.Is(err, syscall.EINVAL) {
+ log.Error(err, "failed to sync audit logger")
+ }
}()As per coding guidelines, Go security requires “Never ignore error returns”. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Initialize OTEL TracerProvider — no-op when endpoint is empty. | ||||||||||||||||||||||
| tp := initTracerProvider(otelEndpoint, otelInsecure, log) | ||||||||||||||||||||||
| defer func() { | ||||||||||||||||||||||
| if err := tp.Shutdown(context.Background()); err != nil { | ||||||||||||||||||||||
|
Comment on lines
+129
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Inspect OTEL init/shutdown call sites and any documented shutdown grace period.
rg -n -C3 'initTracerProvider|Shutdown\(context\.Background\(\)|otlptracegrpc\.New|terminationGracePeriodSeconds' cmd/main.go config/ .ai/spec/ README.mdRepository: openshift/lightspeed-agentic-operator Length of output: 1158 🏁 Script executed: #!/bin/bash
# Read the full initTracerProvider function to see ctx definition and usage
sed -n '163,203p' cmd/main.goRepository: openshift/lightspeed-agentic-operator Length of output: 1325 🏁 Script executed: #!/bin/bash
# Check for timeout patterns in main.go and deployment config
echo "=== Timeout patterns in main.go ==="
grep -n "WithTimeout\|time.Second\|context.WithDeadline" cmd/main.go
echo -e "\n=== Deployment terminationGracePeriodSeconds ==="
find config/ -name "*.yaml" -o -name "*.yml" | xargs grep -l "terminationGracePeriodSeconds" 2>/dev/null || echo "Not found in config/"
echo -e "\n=== Manager deployment configuration ==="
cat config/manager/manager.yaml 2>/dev/null | head -80Repository: openshift/lightspeed-agentic-operator Length of output: 2181 🏁 Script executed: #!/bin/bash
# Check go.mod for OTEL dependencies
echo "=== OTEL dependencies in go.mod ==="
grep -i "otel\|trace" go.mod
echo -e "\n=== Check for OTEL SDK timeout behavior in imports ==="
grep -n "otlptracegrpc\|sdktrace\|context" cmd/main.go | head -20Repository: openshift/lightspeed-agentic-operator Length of output: 1319 Add timeout contexts for OTEL exporter creation and shutdown. Exporter creation at line 180 and shutdown at line 130 use Suggested fix defer func() {
- if err := tp.Shutdown(context.Background()); err != nil {
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ if err := tp.Shutdown(shutdownCtx); err != nil {
log.Error(err, "failed to shut down tracer provider")
}
}()- ctx := context.Background()
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
opts := []otlptracegrpc.Option{
otlptracegrpc.WithEndpoint(endpoint),
}🤖 Prompt for AI AgentsSource: Coding guidelines |
||||||||||||||||||||||
| log.Error(err, "failed to shut down tracer provider") | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if err := agenticcontroller.Setup(mgr, agenticcontroller.Options{ | ||||||||||||||||||||||
| Namespace: namespace, | ||||||||||||||||||||||
| AgenticConsoleImage: agenticConsoleImage, | ||||||||||||||||||||||
| AgenticSandboxImage: agenticSandboxImage, | ||||||||||||||||||||||
| SandboxMode: sandboxMode, | ||||||||||||||||||||||
| ImagePullPolicy: imagePullPolicy, | ||||||||||||||||||||||
| Audit: auditLogger, | ||||||||||||||||||||||
| }); err != nil { | ||||||||||||||||||||||
| log.Error(err, "unable to set up agentic controllers") | ||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||
|
|
@@ -107,9 +153,51 @@ func main() { | |||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| log.Info("starting manager", "namespace", namespace) | ||||||||||||||||||||||
| log.Info("starting manager", "namespace", namespace, "auditLogging", auditLogging, "otelEndpoint", otelEndpoint) | ||||||||||||||||||||||
| if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { | ||||||||||||||||||||||
| log.Error(err, "problem running manager") | ||||||||||||||||||||||
| os.Exit(1) | ||||||||||||||||||||||
|
Comment on lines
157
to
159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Flush telemetry before exiting on manager errors.
Suggested pattern- if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
- log.Error(err, "problem running manager")
- os.Exit(1)
- }
+ if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
+ log.Error(err, "problem running manager")
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ if shutdownErr := tp.Shutdown(shutdownCtx); shutdownErr != nil {
+ log.Error(shutdownErr, "failed to shut down tracer provider")
+ }
+ cancel()
+ if auditLogger != nil {
+ if syncErr := auditLogger.Sync(); syncErr != nil {
+ log.Error(syncErr, "failed to sync audit logger")
+ }
+ }
+ os.Exit(1)
+ }🤖 Prompt for AI Agents |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| func initTracerProvider(endpoint string, insecure bool, log logr.Logger) *sdktrace.TracerProvider { | ||||||||||||||||||||||
| otel.SetTextMapPropagator(propagation.TraceContext{}) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if endpoint == "" { | ||||||||||||||||||||||
| tp := sdktrace.NewTracerProvider() | ||||||||||||||||||||||
| otel.SetTracerProvider(tp) | ||||||||||||||||||||||
| log.Info("OTEL tracer: no-op (no endpoint configured)") | ||||||||||||||||||||||
| return tp | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ctx := context.Background() | ||||||||||||||||||||||
| opts := []otlptracegrpc.Option{ | ||||||||||||||||||||||
| otlptracegrpc.WithEndpoint(endpoint), | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if insecure { | ||||||||||||||||||||||
| opts = append(opts, otlptracegrpc.WithInsecure()) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| exporter, err := otlptracegrpc.New(ctx, opts...) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| log.Error(err, "failed to create OTLP exporter, falling back to no-op") | ||||||||||||||||||||||
| tp := sdktrace.NewTracerProvider() | ||||||||||||||||||||||
| otel.SetTracerProvider(tp) | ||||||||||||||||||||||
| return tp | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| res, mergeErr := resource.Merge( | ||||||||||||||||||||||
| resource.Default(), | ||||||||||||||||||||||
| resource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("agentic-operator")), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| if mergeErr != nil { | ||||||||||||||||||||||
| log.Error(mergeErr, "failed to merge OTEL resources, falling back to default resource") | ||||||||||||||||||||||
| res = resource.Default() | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| tp := sdktrace.NewTracerProvider( | ||||||||||||||||||||||
| sdktrace.WithBatcher(exporter), | ||||||||||||||||||||||
| sdktrace.WithResource(res), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| otel.SetTracerProvider(tp) | ||||||||||||||||||||||
| log.Info("OTEL tracer: OTLP exporter configured", "endpoint", endpoint, "insecure", insecure) | ||||||||||||||||||||||
| return tp | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle audit config read failures explicitly.
Line 111 silently falls back for every
Geterror, so RBAC/API failures can disable configured OTEL tracing or apply the wrong audit logging mode without any signal. Default only forNotFound; log/fail closed for other errors, and bound the API call with a timeout.Suggested fix
As per coding guidelines, Go security requires “Never ignore error returns” and “context.Context for cancellation and timeouts”.
🤖 Prompt for AI Agents
Source: Coding guidelines