VYPR
Moderate severityNVD Advisory· Published Oct 13, 2025· Updated Oct 14, 2025

Omni is Vulnerable to DoS via Empty Create/Update Resource Requests

CVE-2025-59836

Description

Omni manages Kubernetes on bare metal, virtual machines, or in a cloud. Prior to 1.1.5 and 1.0.2, there is a nil pointer dereference vulnerability in the Omni Resource Service allows unauthenticated users to cause a server panic and denial of service by sending empty create/update resource requests through the API endpoints. The vulnerability exists in the isSensitiveSpec function which calls grpcomni.CreateResource without checking if the resource's metadata field is nil. When a resource is created with an empty Metadata field, the CreateResource function attempts to access resource.Metadata.Version causing a segmentation fault. This vulnerability is fixed in 1.1.5 and 1.0.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/siderolabs/omniGo
>= 1.1.0-beta.0, < 1.1.51.1.5
github.com/siderolabs/omniGo
< 1.0.21.0.2

Affected products

1

Patches

2
1396083f766a

fix: fix the order in the grpc interceptor chain

https://github.com/siderolabs/omniUtku OzdemirSep 22, 2025via ghsa
11 files changed · +80 49
  • go.mod+1 1 modified
    @@ -66,7 +66,7 @@ require (
     	github.com/siderolabs/discovery-client v0.1.13
     	github.com/siderolabs/discovery-service v1.0.11
     	github.com/siderolabs/gen v0.8.5
    -	github.com/siderolabs/go-api-signature v0.3.7
    +	github.com/siderolabs/go-api-signature v0.3.8
     	github.com/siderolabs/go-circular v0.2.3
     	github.com/siderolabs/go-debug v0.6.0
     	github.com/siderolabs/go-kubernetes v0.2.25
    
  • go.sum+2 2 modified
    @@ -409,8 +409,8 @@ github.com/siderolabs/discovery-service v1.0.11 h1:+ymDXKhPL2f1c5MIO559wciA38PcQ
     github.com/siderolabs/discovery-service v1.0.11/go.mod h1:pUTOYgtYasO/T02zJNNfw0SCP8hDbIgFIvdm+Fn1UKo=
     github.com/siderolabs/gen v0.8.5 h1:xlWXTynnGD/epaj7uplvKvmAkBH+Fp51bLnw1JC0xME=
     github.com/siderolabs/gen v0.8.5/go.mod h1:CRrktDXQf3yDJI7xKv+cDYhBbKdfd/YE16OpgcHoT9E=
    -github.com/siderolabs/go-api-signature v0.3.7 h1:Qx5NH3BrtYucCgiLObAJhx7pouLR4tivr1moOClII3M=
    -github.com/siderolabs/go-api-signature v0.3.7/go.mod h1:MQy+DcXCQIFFXZr+E4tbMmnQSQs7WpubSpJFRN694mI=
    +github.com/siderolabs/go-api-signature v0.3.8 h1:0iTcOWIxOAc7M8aB2L+WScUd4BoqdXshvQ4h9tSSeF8=
    +github.com/siderolabs/go-api-signature v0.3.8/go.mod h1:MQy+DcXCQIFFXZr+E4tbMmnQSQs7WpubSpJFRN694mI=
     github.com/siderolabs/go-circular v0.2.3 h1:GKkA1Tw79kEFGtWdl7WTxEUTbwtklITeiRT0V1McHrA=
     github.com/siderolabs/go-circular v0.2.3/go.mod h1:YBN/q9YpQphUYnBtBgPsngauSHj1TEZfgQZWZVjk1WE=
     github.com/siderolabs/go-debug v0.6.0 h1:wcftcXv3fFeUHwsj4bJpHaXRJ6JJXL+eeaY69fCtHoY=
    
  • hack/generate-certs/main.go+1 1 modified
    @@ -157,7 +157,7 @@ func generate() (err error) {
     }
     
     func runApp(app string, args ...string) error {
    -	cmd := exec.Command(app, args...)
    +	cmd := exec.Command(app, args...) //nolint:noctx
     	cmd.Stdout = os.Stdout
     	cmd.Stderr = os.Stderr
     	cmd.Stdin = os.Stdin
    
  • internal/backend/grpc/resource.go+9 1 modified
    @@ -317,7 +317,7 @@ func (s *ResourceServer) Teardown(ctx context.Context, in *resources.DeleteReque
     
     func getSource(ctx context.Context) common.Runtime {
     	if md, ok := metadata.FromIncomingContext(ctx); ok {
    -		source := md.Get(message.RuntimeHeaderHey)
    +		source := md.Get(message.RuntimeHeaderKey)
     		if source != nil {
     			if res, ok := common.Runtime_value[source[0]]; ok {
     				return common.Runtime(res)
    @@ -361,6 +361,14 @@ func withResource(r res) []runtime.QueryOption {
     
     // CreateResource creates a resource from a resource proto representation.
     func CreateResource(resource *resources.Resource) (cosiresource.Resource, error) { //nolint:ireturn
    +	if resource == nil {
    +		return nil, errors.New("resource is nil")
    +	}
    +
    +	if resource.Metadata == nil {
    +		return nil, errors.New("resource metadata is nil")
    +	}
    +
     	if resource.Metadata.Version == "" {
     		resource.Metadata.Version = "1"
     	}
    
  • internal/backend/grpc/router/router.go+1 1 modified
    @@ -154,7 +154,7 @@ func (r *Router) Director(ctx context.Context, fullMethodName string) (proxy.Mod
     		return proxy.One2One, []proxy.Backend{r.omniBackend}, nil
     	}
     
    -	if runtime := md.Get(message.RuntimeHeaderHey); runtime != nil && runtime[0] == common.Runtime_Talos.String() {
    +	if runtime := md.Get(message.RuntimeHeaderKey); runtime != nil && runtime[0] == common.Runtime_Talos.String() {
     		backends, err := r.getTalosBackend(ctx, md)
     		if err != nil {
     			return proxy.One2One, nil, err
    
  • internal/backend/server.go+7 7 modified
    @@ -416,6 +416,8 @@ func (s *Server) buildServerOptions() ([]grpc.ServerOption, error) {
     		grpc_ctxtags.UnaryServerInterceptor(),
     		logLevelOverrideUnaryInterceptor,
     		grpc_zap.UnaryServerInterceptor(s.logger, grpc_zap.WithMessageProducer(messageProducer)),
    +		grpc_prometheus.UnaryServerInterceptor,
    +		grpc_recovery.UnaryServerInterceptor(recoveryOpt),
     		grpcutil.SetUserAgent(),
     		grpcutil.SetRealPeerAddress(),
     		grpcutil.SetAuditData(),
    @@ -428,14 +430,14 @@ func (s *Server) buildServerOptions() ([]grpc.ServerOption, error) {
     			),
     			1024,
     		),
    -		grpc_prometheus.UnaryServerInterceptor,
    -		grpc_recovery.UnaryServerInterceptor(recoveryOpt),
     	}
     
     	streamInterceptors := []grpc.StreamServerInterceptor{
     		grpc_ctxtags.StreamServerInterceptor(),
     		logLevelOverrideStreamInterceptor,
     		grpc_zap.StreamServerInterceptor(s.logger, grpc_zap.WithMessageProducer(messageProducer)),
    +		grpc_prometheus.StreamServerInterceptor,
    +		grpc_recovery.StreamServerInterceptor(recoveryOpt),
     		grpcutil.StreamSetUserAgent(),
     		grpcutil.StreamSetRealPeerAddress(),
     		grpcutil.StreamSetAuditData(),
    @@ -452,8 +454,6 @@ func (s *Server) buildServerOptions() ([]grpc.ServerOption, error) {
     				),
     			},
     		),
    -		grpc_prometheus.StreamServerInterceptor,
    -		grpc_recovery.StreamServerInterceptor(recoveryOpt),
     	}
     
     	authInterceptors, err := s.getAuthInterceptors()
    @@ -759,12 +759,12 @@ func resourceServerUpdate(resCopy *resapi.UpdateRequest) (*resapi.UpdateRequest,
     func isSensitiveResource(res *v1alpha1.Resource) bool {
     	protoR, err := protobuf.Unmarshal(res)
     	if err != nil {
    -		return false
    +		return true
     	}
     
     	properResource, err := protobuf.UnmarshalResource(protoR)
     	if err != nil {
    -		return false
    +		return true
     	}
     
     	resDef, ok := properResource.(meta.ResourceDefinitionProvider)
    @@ -779,7 +779,7 @@ func isSensitiveResource(res *v1alpha1.Resource) bool {
     func isSensitiveSpec(resource *resapi.Resource) bool {
     	res, err := grpcomni.CreateResource(resource)
     	if err != nil {
    -		return false
    +		return true
     	}
     
     	resDef, ok := res.(meta.ResourceDefinitionProvider)
    
  • internal/integration/auth_test.go+8 12 modified
    @@ -945,11 +945,6 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
     				resource:       omni.NewImagePullStatus(resources.DefaultNamespace, uuid.New().String()),
     				allowedVerbSet: readOnlyVerbSet,
     			},
    -			{
    -				resource:       authres.NewAuthConfig(),
    -				allowedVerbSet: readOnlyVerbSet,
    -				isPublic:       true,
    -			},
     			{
     				resource:       siderolink.NewConnectionParams(resources.DefaultNamespace, uuid.New().String()),
     				allowedVerbSet: readOnlyVerbSet,
    @@ -1155,6 +1150,7 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
     		// delete excluded resources from the untested set
     		delete(untestedResourceTypes, k8s.KubernetesResourceType)
     		delete(untestedResourceTypes, siderolink.DeprecatedLinkCounterType)
    +		delete(untestedResourceTypes, authres.AuthConfigType)
     
     		for _, tc := range testCases {
     			for _, testVerb := range allVerbs {
    @@ -1179,19 +1175,19 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
     
     						accessErr := accessResource(noSignatureCtx, t, rootCli, scopedCli, tc.resource, testVerb)
     
    -						if len(tc.allowedVerbSet) == 0 {
    -							assert.ErrorContains(t, accessErr, "no access is permitted")
    -
    -							return
    -						}
    -
     						if !tc.isPublic {
    -							assert.ErrorContains(t, accessErr, "missing valid signature")
    +							assert.ErrorContains(t, accessErr, "invalid signature")
     
     							// refresh the error but with a signature this time
     							accessErr = accessResource(rootCtx, t, rootCli, scopedCli, tc.resource, testVerb)
     						}
     
    +						if len(tc.allowedVerbSet) == 0 {
    +							assert.ErrorContains(t, accessErr, "no access is permitted")
    +
    +							return
    +						}
    +
     						isVerbError := accessErr != nil && strings.Contains(accessErr.Error(), "only") && strings.Contains(accessErr.Error(), "access is permitted")
     						isRoleError := accessErr != nil && strings.Contains(accessErr.Error(), "insufficient role:")
     
    
  • internal/pkg/auth/handler/handler_test.go+16 6 modified
    @@ -32,7 +32,7 @@ func testHandler(t *testing.T, authEnabled bool) {
     
     	logger := zaptest.NewLogger(t)
     
    -	authenticatorFunc := func(context.Context, string) (*auth.Authenticator, error) {
    +	authenticatorFunc := func(context.Context, string) (*auth.Authenticator, error) { //nolint:unparam
     		return &auth.Authenticator{
     			Verifier: mockSignerVerifier{},
     			Identity: "user@example.com",
    @@ -41,12 +41,15 @@ func testHandler(t *testing.T, authEnabled bool) {
     		}, nil
     	}
     
    -	wrapWithAuth := func(h http.Handler) http.Handler {
    -		return handler.NewAuthConfig(handler.NewSignature(h, authenticatorFunc, logger), authEnabled, logger)
    -	}
    +	testServer := func(signatureRequired message.SignatureRequiredCheckFunc) *httptest.Server {
    +		wrapWithAuth := func(h http.Handler) http.Handler {
    +			signatureHandler := handler.NewSignature(h, authenticatorFunc, logger, message.WithSignatureRequiredCheck(signatureRequired))
     
    -	ts := httptest.NewServer(wrapWithAuth(coreHandler))
    -	defer ts.Close()
    +			return handler.NewAuthConfig(signatureHandler, authEnabled, logger)
    +		}
    +
    +		return httptest.NewServer(wrapWithAuth(coreHandler))
    +	}
     
     	ctx := t.Context()
     
    @@ -59,6 +62,7 @@ func testHandler(t *testing.T, authEnabled bool) {
     		verifyContext   bool
     
     		expectedCode int
    +		public       bool
     	}
     
     	var testCases []testCase
    @@ -69,6 +73,7 @@ func testHandler(t *testing.T, authEnabled bool) {
     				name:         "no signature",
     				expectedCode: http.StatusOK,
     				uri:          "/ok",
    +				public:       true,
     			},
     			{
     				name:         "correct signature",
    @@ -101,6 +106,11 @@ func testHandler(t *testing.T, authEnabled bool) {
     
     	for _, tc := range testCases {
     		t.Run(tc.name, func(t *testing.T) {
    +			ts := testServer(func() (bool, error) {
    +				return authEnabled && !tc.public, nil
    +			})
    +			defer ts.Close()
    +
     			req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL+tc.uri, nil)
     			require.NoError(t, err)
     
    
  • internal/pkg/auth/handler/signature.go+4 2 modified
    @@ -25,14 +25,16 @@ type Signature struct {
     	authenticatorFunc auth.AuthenticatorFunc
     	next              http.Handler
     	logger            *zap.Logger
    +	options           []message.Option
     }
     
     // NewSignature returns a new signature handler.
    -func NewSignature(handler http.Handler, authenticatorFunc auth.AuthenticatorFunc, logger *zap.Logger) *Signature {
    +func NewSignature(handler http.Handler, authenticatorFunc auth.AuthenticatorFunc, logger *zap.Logger, messageOptions ...message.Option) *Signature {
     	return &Signature{
     		next:              handler,
     		authenticatorFunc: authenticatorFunc,
     		logger:            logger,
    +		options:           messageOptions,
     	}
     }
     
    @@ -57,7 +59,7 @@ func (s *Signature) ServeHTTP(writer http.ResponseWriter, request *http.Request)
     }
     
     func (s *Signature) intercept(request *http.Request) (*http.Request, error) {
    -	msg, err := message.NewHTTP(request)
    +	msg, err := message.NewHTTP(request, s.options...)
     	if err != nil {
     		return nil, err
     	}
    
  • internal/pkg/auth/interceptor/auth_config.go+16 4 modified
    @@ -14,6 +14,8 @@ import (
     	"google.golang.org/grpc"
     	"google.golang.org/grpc/metadata"
     
    +	resapi "github.com/siderolabs/omni/client/api/omni/resources"
    +	authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
     	"github.com/siderolabs/omni/internal/backend/runtime/omni/audit"
     	"github.com/siderolabs/omni/internal/pkg/auth"
     	"github.com/siderolabs/omni/internal/pkg/ctxstore"
    @@ -36,7 +38,15 @@ func NewAuthConfig(enabled bool, logger *zap.Logger) *AuthConfig {
     // Unary returns a new unary GRPC interceptor.
     func (c *AuthConfig) Unary() grpc.UnaryServerInterceptor {
     	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    -		ctx = c.intercept(ctx, info.FullMethod)
    +		isGetAuthConfigRequest := false
    +
    +		if req != nil && info != nil && info.FullMethod == resapi.ResourceService_Get_FullMethodName {
    +			if getReq, getReqOk := req.(*resapi.GetRequest); getReqOk && getReq.Type == authres.AuthConfigType {
    +				isGetAuthConfigRequest = true
    +			}
    +		}
    +
    +		ctx = c.intercept(ctx, isGetAuthConfigRequest, info.FullMethod)
     
     		return handler(ctx, req)
     	}
    @@ -45,7 +55,7 @@ func (c *AuthConfig) Unary() grpc.UnaryServerInterceptor {
     // Stream returns a new streaming GRPC interceptor.
     func (c *AuthConfig) Stream() grpc.StreamServerInterceptor {
     	return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    -		ctx := c.intercept(ss.Context(), info.FullMethod)
    +		ctx := c.intercept(ss.Context(), false, info.FullMethod)
     
     		return handler(srv, &grpc_middleware.WrappedServerStream{
     			ServerStream:   ss,
    @@ -54,7 +64,7 @@ func (c *AuthConfig) Stream() grpc.StreamServerInterceptor {
     	}
     }
     
    -func (c *AuthConfig) intercept(ctx context.Context, method string) context.Context {
    +func (c *AuthConfig) intercept(ctx context.Context, isGetAuthConfigRequest bool, method string) context.Context {
     	ctx = ctxstore.WithValue(ctx, auth.EnabledAuthContextKey{Enabled: c.enabled})
     
     	if !c.enabled {
    @@ -66,7 +76,9 @@ func (c *AuthConfig) intercept(ctx context.Context, method string) context.Conte
     		md = metadata.New(nil)
     	}
     
    -	msg := message.NewGRPC(md, method)
    +	msg := message.NewGRPC(md, method, message.WithSignatureRequiredCheck(func() (bool, error) {
    +		return !isGetAuthConfigRequest, nil
    +	}))
     
     	auditData, ok := ctxstore.Value[*audit.Data](ctx)
     	if ok {
    
  • internal/pkg/auth/interceptor/signature_test.go+15 12 modified
    @@ -22,28 +22,29 @@ import (
     	"google.golang.org/grpc"
     	"google.golang.org/grpc/codes"
     	"google.golang.org/grpc/credentials/insecure"
    -	"google.golang.org/grpc/interop/grpc_testing"
     	"google.golang.org/grpc/metadata"
     	"google.golang.org/grpc/status"
     
    +	"github.com/siderolabs/omni/client/api/omni/resources"
    +	authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
     	"github.com/siderolabs/omni/internal/pkg/auth"
     	"github.com/siderolabs/omni/internal/pkg/auth/interceptor"
     	"github.com/siderolabs/omni/internal/pkg/auth/role"
     	"github.com/siderolabs/omni/internal/pkg/test"
     )
     
     type testServer struct {
    -	grpc_testing.UnimplementedTestServiceServer
    +	resources.UnimplementedResourceServiceServer
     
     	t *testing.T
     }
     
    -func (s testServer) UnaryCall(_ context.Context, _ *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) {
    -	return &grpc_testing.SimpleResponse{}, nil
    +func (s testServer) Get(context.Context, *resources.GetRequest) (*resources.GetResponse, error) {
    +	return &resources.GetResponse{}, nil
     }
     
     type SignatureTestSuite struct {
    -	testServiceClient grpc_testing.TestServiceClient
    +	testServiceClient resources.ResourceServiceClient
     	clientConn        *grpc.ClientConn
     	key               *pgp.Key
     	test.GRPCSuite
    @@ -81,7 +82,7 @@ func (suite *SignatureTestSuite) SetupSuite() {
     		),
     	)
     
    -	grpc_testing.RegisterTestServiceServer(suite.Server, testServer{
    +	resources.RegisterResourceServiceServer(suite.Server, testServer{
     		t: suite.T(),
     	})
     
    @@ -94,7 +95,7 @@ func (suite *SignatureTestSuite) SetupSuite() {
     	suite.clientConn, err = grpc.NewClient(suite.Target, dialOptions...)
     	suite.Require().NoError(err)
     
    -	suite.testServiceClient = grpc_testing.NewTestServiceClient(suite.clientConn)
    +	suite.testServiceClient = resources.NewResourceServiceClient(suite.clientConn)
     }
     
     func (suite *SignatureTestSuite) TearDownSuite() {
    @@ -103,7 +104,9 @@ func (suite *SignatureTestSuite) TearDownSuite() {
     }
     
     func (suite *SignatureTestSuite) TestMissingSignaturePassthrough() {
    -	_, err := suite.testServiceClient.UnaryCall(suite.T().Context(), &grpc_testing.SimpleRequest{})
    +	_, err := suite.testServiceClient.Get(suite.T().Context(), &resources.GetRequest{
    +		Type: authres.AuthConfigType,
    +	})
     
     	suite.Assert().NoError(err)
     }
    @@ -113,7 +116,7 @@ func (suite *SignatureTestSuite) TestInvalidSignatureVersion() {
     		message.SignatureHeaderKey, "invalid",
     	))
     
    -	_, err := suite.testServiceClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{})
    +	_, err := suite.testServiceClient.Get(ctx, &resources.GetRequest{})
     
     	suite.Assert().Error(err)
     	suite.Assert().Equal(codes.Unauthenticated, status.Code(err), "error code should be codes.Unauthenticated")
    @@ -127,7 +130,7 @@ func (suite *SignatureTestSuite) TestMissingTimestamp() {
     		message.SignatureHeaderKey, fmt.Sprintf("%s test@example.org signer-1 %s", message.SignatureVersionV1, payload),
     	))
     
    -	_, err := suite.testServiceClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{})
    +	_, err := suite.testServiceClient.Get(ctx, &resources.GetRequest{})
     
     	suite.Assert().Error(err)
     	suite.Assert().Equal(codes.Unauthenticated, status.Code(err), "error code should be codes.Unauthenticated")
    @@ -141,7 +144,7 @@ func (suite *SignatureTestSuite) TestValidSignature() {
     		Headers: map[string][]string{
     			message.TimestampHeaderKey: {epochTimestamp},
     		},
    -		Method: "/grpc.testing.TestService/UnaryCall",
    +		Method: resources.ResourceService_Get_FullMethodName,
     	}
     
     	payloadJSON, err := json.Marshal(payload)
    @@ -163,7 +166,7 @@ func (suite *SignatureTestSuite) TestValidSignature() {
     		message.PayloadHeaderKey, string(payloadJSON),
     	))
     
    -	_, err = suite.testServiceClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{})
    +	_, err = suite.testServiceClient.Get(ctx, &resources.GetRequest{})
     
     	assert.NoError(suite.T(), err)
     }
    
1fd954af6498

fix: fix the order in the grpc interceptor chain

https://github.com/siderolabs/omniUtku OzdemirSep 22, 2025via ghsa
11 files changed · +80 49
  • go.mod+1 1 modified
    @@ -66,7 +66,7 @@ require (
     	github.com/siderolabs/discovery-client v0.1.11
     	github.com/siderolabs/discovery-service v1.0.10
     	github.com/siderolabs/gen v0.8.4
    -	github.com/siderolabs/go-api-signature v0.3.6
    +	github.com/siderolabs/go-api-signature v0.3.8
     	github.com/siderolabs/go-circular v0.2.3
     	github.com/siderolabs/go-debug v0.5.0
     	github.com/siderolabs/go-kubernetes v0.2.23
    
  • go.sum+2 2 modified
    @@ -415,8 +415,8 @@ github.com/siderolabs/discovery-service v1.0.10 h1:GSd5p+bC+PJjIpCqiDtVFKKU18Lps
     github.com/siderolabs/discovery-service v1.0.10/go.mod h1:tzeHcfftQQHZSShuSTcrgIN3BY6fmhlum/Z9yOJ61lk=
     github.com/siderolabs/gen v0.8.4 h1:1Xj/YvKTXgpnr9ZC7heKcskJo5wHvWOybwjQSCfEmsQ=
     github.com/siderolabs/gen v0.8.4/go.mod h1:CRrktDXQf3yDJI7xKv+cDYhBbKdfd/YE16OpgcHoT9E=
    -github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU=
    -github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U=
    +github.com/siderolabs/go-api-signature v0.3.8 h1:0iTcOWIxOAc7M8aB2L+WScUd4BoqdXshvQ4h9tSSeF8=
    +github.com/siderolabs/go-api-signature v0.3.8/go.mod h1:MQy+DcXCQIFFXZr+E4tbMmnQSQs7WpubSpJFRN694mI=
     github.com/siderolabs/go-circular v0.2.3 h1:GKkA1Tw79kEFGtWdl7WTxEUTbwtklITeiRT0V1McHrA=
     github.com/siderolabs/go-circular v0.2.3/go.mod h1:YBN/q9YpQphUYnBtBgPsngauSHj1TEZfgQZWZVjk1WE=
     github.com/siderolabs/go-debug v0.5.0 h1:AQwFtvyFkSYTA1of4/UyDvVu8dVLoQP5sUYgmcp/u+4=
    
  • hack/generate-certs/main.go+1 1 modified
    @@ -157,7 +157,7 @@ func generate() (err error) {
     }
     
     func runApp(app string, args ...string) error {
    -	cmd := exec.Command(app, args...)
    +	cmd := exec.Command(app, args...) //nolint:noctx
     	cmd.Stdout = os.Stdout
     	cmd.Stderr = os.Stderr
     	cmd.Stdin = os.Stdin
    
  • internal/backend/grpc/resource.go+9 1 modified
    @@ -317,7 +317,7 @@ func (s *ResourceServer) Teardown(ctx context.Context, in *resources.DeleteReque
     
     func getSource(ctx context.Context) common.Runtime {
     	if md, ok := metadata.FromIncomingContext(ctx); ok {
    -		source := md.Get(message.RuntimeHeaderHey)
    +		source := md.Get(message.RuntimeHeaderKey)
     		if source != nil {
     			if res, ok := common.Runtime_value[source[0]]; ok {
     				return common.Runtime(res)
    @@ -361,6 +361,14 @@ func withResource(r res) []runtime.QueryOption {
     
     // CreateResource creates a resource from a resource proto representation.
     func CreateResource(resource *resources.Resource) (cosiresource.Resource, error) { //nolint:ireturn
    +	if resource == nil {
    +		return nil, errors.New("resource is nil")
    +	}
    +
    +	if resource.Metadata == nil {
    +		return nil, errors.New("resource metadata is nil")
    +	}
    +
     	if resource.Metadata.Version == "" {
     		resource.Metadata.Version = "1"
     	}
    
  • internal/backend/grpc/router/router.go+1 1 modified
    @@ -154,7 +154,7 @@ func (r *Router) Director(ctx context.Context, fullMethodName string) (proxy.Mod
     		return proxy.One2One, []proxy.Backend{r.omniBackend}, nil
     	}
     
    -	if runtime := md.Get(message.RuntimeHeaderHey); runtime != nil && runtime[0] == common.Runtime_Talos.String() {
    +	if runtime := md.Get(message.RuntimeHeaderKey); runtime != nil && runtime[0] == common.Runtime_Talos.String() {
     		backends, err := r.getTalosBackend(ctx, md)
     		if err != nil {
     			return proxy.One2One, nil, err
    
  • internal/backend/server.go+7 7 modified
    @@ -416,6 +416,8 @@ func (s *Server) buildServerOptions() ([]grpc.ServerOption, error) {
     		grpc_ctxtags.UnaryServerInterceptor(),
     		logLevelOverrideUnaryInterceptor,
     		grpc_zap.UnaryServerInterceptor(s.logger, grpc_zap.WithMessageProducer(messageProducer)),
    +		grpc_prometheus.UnaryServerInterceptor,
    +		grpc_recovery.UnaryServerInterceptor(recoveryOpt),
     		grpcutil.SetUserAgent(),
     		grpcutil.SetRealPeerAddress(),
     		grpcutil.SetAuditData(),
    @@ -428,14 +430,14 @@ func (s *Server) buildServerOptions() ([]grpc.ServerOption, error) {
     			),
     			1024,
     		),
    -		grpc_prometheus.UnaryServerInterceptor,
    -		grpc_recovery.UnaryServerInterceptor(recoveryOpt),
     	}
     
     	streamInterceptors := []grpc.StreamServerInterceptor{
     		grpc_ctxtags.StreamServerInterceptor(),
     		logLevelOverrideStreamInterceptor,
     		grpc_zap.StreamServerInterceptor(s.logger, grpc_zap.WithMessageProducer(messageProducer)),
    +		grpc_prometheus.StreamServerInterceptor,
    +		grpc_recovery.StreamServerInterceptor(recoveryOpt),
     		grpcutil.StreamSetUserAgent(),
     		grpcutil.StreamSetRealPeerAddress(),
     		grpcutil.StreamSetAuditData(),
    @@ -452,8 +454,6 @@ func (s *Server) buildServerOptions() ([]grpc.ServerOption, error) {
     				),
     			},
     		),
    -		grpc_prometheus.StreamServerInterceptor,
    -		grpc_recovery.StreamServerInterceptor(recoveryOpt),
     	}
     
     	authInterceptors, err := s.getAuthInterceptors()
    @@ -759,12 +759,12 @@ func resourceServerUpdate(resCopy *resapi.UpdateRequest) (*resapi.UpdateRequest,
     func isSensitiveResource(res *v1alpha1.Resource) bool {
     	protoR, err := protobuf.Unmarshal(res)
     	if err != nil {
    -		return false
    +		return true
     	}
     
     	properResource, err := protobuf.UnmarshalResource(protoR)
     	if err != nil {
    -		return false
    +		return true
     	}
     
     	resDef, ok := properResource.(meta.ResourceDefinitionProvider)
    @@ -779,7 +779,7 @@ func isSensitiveResource(res *v1alpha1.Resource) bool {
     func isSensitiveSpec(resource *resapi.Resource) bool {
     	res, err := grpcomni.CreateResource(resource)
     	if err != nil {
    -		return false
    +		return true
     	}
     
     	resDef, ok := res.(meta.ResourceDefinitionProvider)
    
  • internal/integration/auth_test.go+8 12 modified
    @@ -940,11 +940,6 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
     				resource:       omni.NewImagePullStatus(resources.DefaultNamespace, uuid.New().String()),
     				allowedVerbSet: readOnlyVerbSet,
     			},
    -			{
    -				resource:       authres.NewAuthConfig(),
    -				allowedVerbSet: readOnlyVerbSet,
    -				isPublic:       true,
    -			},
     			{
     				resource:       siderolink.NewConnectionParams(resources.DefaultNamespace, uuid.New().String()),
     				allowedVerbSet: readOnlyVerbSet,
    @@ -1149,6 +1144,7 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
     		// delete excluded resources from the untested set
     		delete(untestedResourceTypes, k8s.KubernetesResourceType)
     		delete(untestedResourceTypes, siderolink.DeprecatedLinkCounterType)
    +		delete(untestedResourceTypes, authres.AuthConfigType)
     
     		for _, tc := range testCases {
     			for _, testVerb := range allVerbs {
    @@ -1173,19 +1169,19 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
     
     						accessErr := accessResource(noSignatureCtx, t, rootCli, scopedCli, tc.resource, testVerb)
     
    -						if len(tc.allowedVerbSet) == 0 {
    -							assert.ErrorContains(t, accessErr, "no access is permitted")
    -
    -							return
    -						}
    -
     						if !tc.isPublic {
    -							assert.ErrorContains(t, accessErr, "missing valid signature")
    +							assert.ErrorContains(t, accessErr, "invalid signature")
     
     							// refresh the error but with a signature this time
     							accessErr = accessResource(rootCtx, t, rootCli, scopedCli, tc.resource, testVerb)
     						}
     
    +						if len(tc.allowedVerbSet) == 0 {
    +							assert.ErrorContains(t, accessErr, "no access is permitted")
    +
    +							return
    +						}
    +
     						isVerbError := accessErr != nil && strings.Contains(accessErr.Error(), "only") && strings.Contains(accessErr.Error(), "access is permitted")
     						isRoleError := accessErr != nil && strings.Contains(accessErr.Error(), "insufficient role:")
     
    
  • internal/pkg/auth/handler/handler_test.go+16 6 modified
    @@ -32,7 +32,7 @@ func testHandler(t *testing.T, authEnabled bool) {
     
     	logger := zaptest.NewLogger(t)
     
    -	authenticatorFunc := func(context.Context, string) (*auth.Authenticator, error) {
    +	authenticatorFunc := func(context.Context, string) (*auth.Authenticator, error) { //nolint:unparam
     		return &auth.Authenticator{
     			Verifier: mockSignerVerifier{},
     			Identity: "user@example.com",
    @@ -41,12 +41,15 @@ func testHandler(t *testing.T, authEnabled bool) {
     		}, nil
     	}
     
    -	wrapWithAuth := func(h http.Handler) http.Handler {
    -		return handler.NewAuthConfig(handler.NewSignature(h, authenticatorFunc, logger), authEnabled, logger)
    -	}
    +	testServer := func(signatureRequired message.SignatureRequiredCheckFunc) *httptest.Server {
    +		wrapWithAuth := func(h http.Handler) http.Handler {
    +			signatureHandler := handler.NewSignature(h, authenticatorFunc, logger, message.WithSignatureRequiredCheck(signatureRequired))
     
    -	ts := httptest.NewServer(wrapWithAuth(coreHandler))
    -	defer ts.Close()
    +			return handler.NewAuthConfig(signatureHandler, authEnabled, logger)
    +		}
    +
    +		return httptest.NewServer(wrapWithAuth(coreHandler))
    +	}
     
     	ctx := t.Context()
     
    @@ -59,6 +62,7 @@ func testHandler(t *testing.T, authEnabled bool) {
     		verifyContext   bool
     
     		expectedCode int
    +		public       bool
     	}
     
     	var testCases []testCase
    @@ -69,6 +73,7 @@ func testHandler(t *testing.T, authEnabled bool) {
     				name:         "no signature",
     				expectedCode: http.StatusOK,
     				uri:          "/ok",
    +				public:       true,
     			},
     			{
     				name:         "correct signature",
    @@ -101,6 +106,11 @@ func testHandler(t *testing.T, authEnabled bool) {
     
     	for _, tc := range testCases {
     		t.Run(tc.name, func(t *testing.T) {
    +			ts := testServer(func() (bool, error) {
    +				return authEnabled && !tc.public, nil
    +			})
    +			defer ts.Close()
    +
     			req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL+tc.uri, nil)
     			require.NoError(t, err)
     
    
  • internal/pkg/auth/handler/signature.go+4 2 modified
    @@ -25,14 +25,16 @@ type Signature struct {
     	authenticatorFunc auth.AuthenticatorFunc
     	next              http.Handler
     	logger            *zap.Logger
    +	options           []message.Option
     }
     
     // NewSignature returns a new signature handler.
    -func NewSignature(handler http.Handler, authenticatorFunc auth.AuthenticatorFunc, logger *zap.Logger) *Signature {
    +func NewSignature(handler http.Handler, authenticatorFunc auth.AuthenticatorFunc, logger *zap.Logger, messageOptions ...message.Option) *Signature {
     	return &Signature{
     		next:              handler,
     		authenticatorFunc: authenticatorFunc,
     		logger:            logger,
    +		options:           messageOptions,
     	}
     }
     
    @@ -57,7 +59,7 @@ func (s *Signature) ServeHTTP(writer http.ResponseWriter, request *http.Request)
     }
     
     func (s *Signature) intercept(request *http.Request) (*http.Request, error) {
    -	msg, err := message.NewHTTP(request)
    +	msg, err := message.NewHTTP(request, s.options...)
     	if err != nil {
     		return nil, err
     	}
    
  • internal/pkg/auth/interceptor/auth_config.go+16 4 modified
    @@ -14,6 +14,8 @@ import (
     	"google.golang.org/grpc"
     	"google.golang.org/grpc/metadata"
     
    +	resapi "github.com/siderolabs/omni/client/api/omni/resources"
    +	authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
     	"github.com/siderolabs/omni/internal/backend/runtime/omni/audit"
     	"github.com/siderolabs/omni/internal/pkg/auth"
     	"github.com/siderolabs/omni/internal/pkg/ctxstore"
    @@ -36,7 +38,15 @@ func NewAuthConfig(enabled bool, logger *zap.Logger) *AuthConfig {
     // Unary returns a new unary GRPC interceptor.
     func (c *AuthConfig) Unary() grpc.UnaryServerInterceptor {
     	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
    -		ctx = c.intercept(ctx, info.FullMethod)
    +		isGetAuthConfigRequest := false
    +
    +		if req != nil && info != nil && info.FullMethod == resapi.ResourceService_Get_FullMethodName {
    +			if getReq, getReqOk := req.(*resapi.GetRequest); getReqOk && getReq.Type == authres.AuthConfigType {
    +				isGetAuthConfigRequest = true
    +			}
    +		}
    +
    +		ctx = c.intercept(ctx, isGetAuthConfigRequest, info.FullMethod)
     
     		return handler(ctx, req)
     	}
    @@ -45,7 +55,7 @@ func (c *AuthConfig) Unary() grpc.UnaryServerInterceptor {
     // Stream returns a new streaming GRPC interceptor.
     func (c *AuthConfig) Stream() grpc.StreamServerInterceptor {
     	return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    -		ctx := c.intercept(ss.Context(), info.FullMethod)
    +		ctx := c.intercept(ss.Context(), false, info.FullMethod)
     
     		return handler(srv, &grpc_middleware.WrappedServerStream{
     			ServerStream:   ss,
    @@ -54,7 +64,7 @@ func (c *AuthConfig) Stream() grpc.StreamServerInterceptor {
     	}
     }
     
    -func (c *AuthConfig) intercept(ctx context.Context, method string) context.Context {
    +func (c *AuthConfig) intercept(ctx context.Context, isGetAuthConfigRequest bool, method string) context.Context {
     	ctx = ctxstore.WithValue(ctx, auth.EnabledAuthContextKey{Enabled: c.enabled})
     
     	if !c.enabled {
    @@ -66,7 +76,9 @@ func (c *AuthConfig) intercept(ctx context.Context, method string) context.Conte
     		md = metadata.New(nil)
     	}
     
    -	msg := message.NewGRPC(md, method)
    +	msg := message.NewGRPC(md, method, message.WithSignatureRequiredCheck(func() (bool, error) {
    +		return !isGetAuthConfigRequest, nil
    +	}))
     
     	auditData, ok := ctxstore.Value[*audit.Data](ctx)
     	if ok {
    
  • internal/pkg/auth/interceptor/signature_test.go+15 12 modified
    @@ -22,28 +22,29 @@ import (
     	"google.golang.org/grpc"
     	"google.golang.org/grpc/codes"
     	"google.golang.org/grpc/credentials/insecure"
    -	"google.golang.org/grpc/interop/grpc_testing"
     	"google.golang.org/grpc/metadata"
     	"google.golang.org/grpc/status"
     
    +	"github.com/siderolabs/omni/client/api/omni/resources"
    +	authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth"
     	"github.com/siderolabs/omni/internal/pkg/auth"
     	"github.com/siderolabs/omni/internal/pkg/auth/interceptor"
     	"github.com/siderolabs/omni/internal/pkg/auth/role"
     	"github.com/siderolabs/omni/internal/pkg/test"
     )
     
     type testServer struct {
    -	grpc_testing.UnimplementedTestServiceServer
    +	resources.UnimplementedResourceServiceServer
     
     	t *testing.T
     }
     
    -func (s testServer) UnaryCall(_ context.Context, _ *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) {
    -	return &grpc_testing.SimpleResponse{}, nil
    +func (s testServer) Get(context.Context, *resources.GetRequest) (*resources.GetResponse, error) {
    +	return &resources.GetResponse{}, nil
     }
     
     type SignatureTestSuite struct {
    -	testServiceClient grpc_testing.TestServiceClient
    +	testServiceClient resources.ResourceServiceClient
     	clientConn        *grpc.ClientConn
     	key               *pgp.Key
     	test.GRPCSuite
    @@ -81,7 +82,7 @@ func (suite *SignatureTestSuite) SetupSuite() {
     		),
     	)
     
    -	grpc_testing.RegisterTestServiceServer(suite.Server, testServer{
    +	resources.RegisterResourceServiceServer(suite.Server, testServer{
     		t: suite.T(),
     	})
     
    @@ -94,7 +95,7 @@ func (suite *SignatureTestSuite) SetupSuite() {
     	suite.clientConn, err = grpc.NewClient(suite.Target, dialOptions...)
     	suite.Require().NoError(err)
     
    -	suite.testServiceClient = grpc_testing.NewTestServiceClient(suite.clientConn)
    +	suite.testServiceClient = resources.NewResourceServiceClient(suite.clientConn)
     }
     
     func (suite *SignatureTestSuite) TearDownSuite() {
    @@ -103,7 +104,9 @@ func (suite *SignatureTestSuite) TearDownSuite() {
     }
     
     func (suite *SignatureTestSuite) TestMissingSignaturePassthrough() {
    -	_, err := suite.testServiceClient.UnaryCall(suite.T().Context(), &grpc_testing.SimpleRequest{})
    +	_, err := suite.testServiceClient.Get(suite.T().Context(), &resources.GetRequest{
    +		Type: authres.AuthConfigType,
    +	})
     
     	suite.Assert().NoError(err)
     }
    @@ -113,7 +116,7 @@ func (suite *SignatureTestSuite) TestInvalidSignatureVersion() {
     		message.SignatureHeaderKey, "invalid",
     	))
     
    -	_, err := suite.testServiceClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{})
    +	_, err := suite.testServiceClient.Get(ctx, &resources.GetRequest{})
     
     	suite.Assert().Error(err)
     	suite.Assert().Equal(codes.Unauthenticated, status.Code(err), "error code should be codes.Unauthenticated")
    @@ -127,7 +130,7 @@ func (suite *SignatureTestSuite) TestMissingTimestamp() {
     		message.SignatureHeaderKey, fmt.Sprintf("%s test@example.org signer-1 %s", message.SignatureVersionV1, payload),
     	))
     
    -	_, err := suite.testServiceClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{})
    +	_, err := suite.testServiceClient.Get(ctx, &resources.GetRequest{})
     
     	suite.Assert().Error(err)
     	suite.Assert().Equal(codes.Unauthenticated, status.Code(err), "error code should be codes.Unauthenticated")
    @@ -141,7 +144,7 @@ func (suite *SignatureTestSuite) TestValidSignature() {
     		Headers: map[string][]string{
     			message.TimestampHeaderKey: {epochTimestamp},
     		},
    -		Method: "/grpc.testing.TestService/UnaryCall",
    +		Method: resources.ResourceService_Get_FullMethodName,
     	}
     
     	payloadJSON, err := json.Marshal(payload)
    @@ -163,7 +166,7 @@ func (suite *SignatureTestSuite) TestValidSignature() {
     		message.PayloadHeaderKey, string(payloadJSON),
     	))
     
    -	_, err = suite.testServiceClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{})
    +	_, err = suite.testServiceClient.Get(ctx, &resources.GetRequest{})
     
     	assert.NoError(suite.T(), err)
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.