mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-04 20:06:18 +02:00
fix: audit trustd code for security
There are no security issues fixed. Drop username/password creds - they were not used. Improve security of token interceptor. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
986e97fc75
commit
9fbb7c95df
@ -104,6 +104,7 @@ func trustdMain() error {
|
||||
®.Registrator{Resources: resources},
|
||||
factory.WithDefaultLog(),
|
||||
factory.WithUnaryInterceptor(creds.UnaryInterceptor()),
|
||||
factory.WithStreamInterceptor(creds.StreamInterceptor()),
|
||||
factory.ServerOptions(
|
||||
grpc.Creds(
|
||||
credentials.NewTLS(serverTLSConfig),
|
||||
|
||||
@ -23,6 +23,7 @@ type Credentials interface {
|
||||
credentials.PerRPCCredentials
|
||||
|
||||
UnaryInterceptor() grpc.UnaryServerInterceptor
|
||||
StreamInterceptor() grpc.StreamServerInterceptor
|
||||
}
|
||||
|
||||
// NewConnection initializes a grpc.ClientConn configured for basic
|
||||
|
||||
@ -6,11 +6,13 @@ package basic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// TokenGetterFunc is the function to dynamically retrieve the token.
|
||||
@ -66,13 +68,23 @@ func (b *TokenCredentials) authenticate(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
if len(md["token"]) > 0 && md["token"][0] == token {
|
||||
return nil
|
||||
}
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return status.Error(codes.Unauthenticated, "missing token")
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s", codes.Unauthenticated.String())
|
||||
if len(md["token"]) == 0 {
|
||||
return status.Error(codes.Unauthenticated, "missing token")
|
||||
}
|
||||
|
||||
incomingTokenHash := sha256.Sum256([]byte(md["token"][0]))
|
||||
expectedTokenHash := sha256.Sum256([]byte(token))
|
||||
|
||||
if subtle.ConstantTimeCompare(incomingTokenHash[:], expectedTokenHash[:]) != 1 {
|
||||
return status.Error(codes.Unauthenticated, "invalid token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnaryInterceptor sets the UnaryServerInterceptor for the server and enforces
|
||||
@ -86,3 +98,14 @@ func (b *TokenCredentials) UnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
|
||||
// StreamInterceptor sets the StreamServerInterceptor for the server and enforces
|
||||
// basic authentication.
|
||||
//
|
||||
// For now, it rejects any API, as we don't have any streaming APIs in trustd component.
|
||||
// This is to prevent accidentally allowing unauthenticated access to streaming APIs in the future without realizing it.
|
||||
func (b *TokenCredentials) StreamInterceptor() grpc.StreamServerInterceptor {
|
||||
return func(any, grpc.ServerStream, *grpc.StreamServerInfo, grpc.StreamHandler) error {
|
||||
return status.Error(codes.Unimplemented, "streaming APIs are not supported")
|
||||
}
|
||||
}
|
||||
|
||||
83
pkg/grpc/middleware/auth/basic/token_test.go
Normal file
83
pkg/grpc/middleware/auth/basic/token_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package basic_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/grpc/middleware/auth/basic"
|
||||
)
|
||||
|
||||
func TestTokenInterceptor(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const validToken = "valid-token"
|
||||
|
||||
creds := basic.NewTokenCredentials(validToken)
|
||||
|
||||
interceptor := creds.UnaryInterceptor()
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
||||
md metadata.MD
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid token",
|
||||
|
||||
md: metadata.MD{
|
||||
"token": []string{validToken},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid token",
|
||||
|
||||
md: metadata.MD{
|
||||
"token": []string{"invalid-token"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing token",
|
||||
md: metadata.MD{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no metadata",
|
||||
md: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := t.Context()
|
||||
|
||||
if test.md != nil {
|
||||
ctx = metadata.NewIncomingContext(ctx, test.md)
|
||||
}
|
||||
|
||||
_, err := interceptor(ctx, nil, nil, func(context.Context, any) (any, error) {
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
if test.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, codes.Unauthenticated, status.Code(err))
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package basic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// UsernameAndPasswordCredentials implements credentials.PerRPCCredentials. It uses a basic
|
||||
// username and password lookup to authenticate users.
|
||||
type UsernameAndPasswordCredentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// NewUsernameAndPasswordCredentials initializes username and password
|
||||
// Credentials.
|
||||
func NewUsernameAndPasswordCredentials(username, password string) (creds Credentials) {
|
||||
creds = &UsernameAndPasswordCredentials{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
return creds
|
||||
}
|
||||
|
||||
// GetRequestMetadata sets the value for the username and password.
|
||||
func (b *UsernameAndPasswordCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"username": b.Username,
|
||||
"password": b.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RequireTransportSecurity is set to true in order to encrypt the
|
||||
// communication.
|
||||
func (b *UsernameAndPasswordCredentials) RequireTransportSecurity() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *UsernameAndPasswordCredentials) authorize(ctx context.Context) error {
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
if len(md["username"]) > 0 && md["username"][0] == b.Username &&
|
||||
len(md["password"]) > 0 && md["password"][0] == b.Password {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s", codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnaryInterceptor sets the UnaryServerInterceptor for the server and enforces
|
||||
// basic authentication.
|
||||
func (b *UsernameAndPasswordCredentials) UnaryInterceptor() grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
start := time.Now()
|
||||
|
||||
if err := b.authorize(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := handler(ctx, req)
|
||||
|
||||
log.Printf("request - Method:%s\tDuration:%s\tError:%v\n",
|
||||
info.FullMethod,
|
||||
time.Since(start),
|
||||
err,
|
||||
)
|
||||
|
||||
return h, err
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package basic_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
//
|
||||
// please remove it once any unit-test is added
|
||||
// for this package
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user