Skip to content

Commit

Permalink
[Feature] Integration Service Authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
ajanikow committed Aug 26, 2024
1 parent 25d636e commit 50f9f2d
Show file tree
Hide file tree
Showing 19 changed files with 953 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- (Feature) Gateway Group for ArangoDeployment
- (Feature) Gateway config loader
- (Feature) ConfigV1 Integration Service
- (Feature) Integration Service Authentication

## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23)
- (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package v1
package definition

const (
Name = "shutdown.v1"
Expand Down
2 changes: 1 addition & 1 deletion integrations/shutdown/v1/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type impl struct {
}

func (i *impl) Name() string {
return Name
return pbShutdownV1.Name
}

func (i *impl) Health() svc.HealthState {
Expand Down
70 changes: 70 additions & 0 deletions pkg/integrations/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package integrations

import (
"context"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"

"github.com/arangodb/kube-arangodb/pkg/util"
)

func basicTokenAuthAuthorize(ctx context.Context, token string) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Errorf(codes.Unauthenticated, "metadata is not provided")
}

values := md[util.AuthorizationGRPCHeader]
if len(values) == 0 {
return status.Errorf(codes.Unauthenticated, "authorization token is not provided")
}

if token != values[0] {
return status.Errorf(codes.Unauthenticated, "invalid token")
}

return nil
}

func basicTokenAuthUnaryInterceptor(token string) grpc.ServerOption {
return grpc.UnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
if err := basicTokenAuthAuthorize(ctx, token); err != nil {
return nil, err
}

return handler(ctx, req)
})
}

func basicTokenAuthStreamInterceptor(token string) grpc.ServerOption {
return grpc.StreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if err := basicTokenAuthAuthorize(ss.Context(), token); err != nil {
return err
}

return handler(srv, ss)
})
}
132 changes: 132 additions & 0 deletions pkg/integrations/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package integrations

import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"

"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
"github.com/arangodb/kube-arangodb/pkg/util/tests/tgrpc"
)

func Test_AuthCases(t *testing.T) {
c, health, internal, external := startService(t,
"--health.auth.type=None",
"--services.external.auth.token=test1",
"--services.external.auth.type=Token",
"--services.auth.token=test2",
"--services.auth.type=Token",
)
defer c.Require(t)

t.Run("Without auth", func(t *testing.T) {
t.Run("health", func(t *testing.T) {
require.NoError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", health),
"--token=",
"client",
"health",
"v1"))
})
t.Run("internal", func(t *testing.T) {
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", internal),
"--token=",
"client",
"health",
"v1")).
Code(t, codes.Unauthenticated).
Errorf(t, "authorization token is not provided")
})
t.Run("external", func(t *testing.T) {
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", external),
"--token=",
"client",
"health",
"v1")).
Code(t, codes.Unauthenticated).
Errorf(t, "authorization token is not provided")
})
})

t.Run("With auth 1", func(t *testing.T) {
t.Run("health", func(t *testing.T) {
require.NoError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", health),
"--token=test1",
"client",
"health",
"v1"))
})
t.Run("internal", func(t *testing.T) {
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", internal),
"--token=test1",
"client",
"health",
"v1")).
Code(t, codes.Unauthenticated).
Errorf(t, "invalid token")
})
t.Run("external", func(t *testing.T) {
require.NoError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", external),
"--token=test1",
"client",
"health",
"v1"))
})
})

t.Run("With auth 2", func(t *testing.T) {
t.Run("health", func(t *testing.T) {
require.NoError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", health),
"--token=test2",
"client",
"health",
"v1"))
})
t.Run("internal", func(t *testing.T) {
require.NoError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", internal),
"--token=test2",
"client",
"health",
"v1"))
})
t.Run("external", func(t *testing.T) {
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
fmt.Sprintf("--address=127.0.0.1:%d", external),
"--token=test2",
"client",
"health",
"v1")).
Code(t, codes.Unauthenticated).
Errorf(t, "invalid token")
})
})
}
83 changes: 83 additions & 0 deletions pkg/integrations/clients/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package clients

import (
"context"
"io"

"github.com/spf13/cobra"
"google.golang.org/grpc"

"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
)

type commandRun[T any] interface {
Register(name, desc string, in func(ctx context.Context, client T) error) commandRun[T]
}

type commandRunImpl[T any] struct {
cmd *cobra.Command
cfg *Config
in func(cc grpc.ClientConnInterface) T
}

func (c commandRunImpl[T]) Register(name, desc string, in func(ctx context.Context, client T) error) commandRun[T] {
c.cmd.AddCommand(&cobra.Command{
Use: name,
Short: desc,
RunE: func(cmd *cobra.Command, args []string) error {
client, closer, err := client(shutdown.Context(), c.cfg, c.in)
if err != nil {
return err
}

defer closer.Close()

return in(shutdown.Context(), client)
},
})
return c
}

func withCommandRun[T any](cmd *cobra.Command, cfg *Config, in func(cc grpc.ClientConnInterface) T) commandRun[T] {
return &commandRunImpl[T]{
cmd: cmd,
cfg: cfg,
in: in,
}
}

func client[T any](ctx context.Context, cfg *Config, in func(cc grpc.ClientConnInterface) T) (T, io.Closer, error) {
var opts []grpc.DialOption

if token := cfg.Token; token != "" {
opts = append(opts, util.TokenAuthInterceptors(token)...)
}

client, closer, err := util.NewGRPCClient(ctx, in, cfg.Address, opts...)
if err != nil {
return util.Default[T](), nil, err
}

return client, closer, nil
}
73 changes: 73 additions & 0 deletions pkg/integrations/clients/health_v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package clients

import (
"github.com/spf13/cobra"
pbHealth "google.golang.org/grpc/health/grpc_health_v1"

"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
)

func init() {
registerer.MustRegister("health/v1", func(cfg *Config) Client {
return &healthV1{
cfg: cfg,
}
})
}

type healthV1 struct {
cfg *Config
}

func (s *healthV1) Name() string {
return "health"
}

func (s *healthV1) Version() string {
return "v1"
}

func (s *healthV1) Register(cmd *cobra.Command) error {
cmd.RunE = func(cmd *cobra.Command, args []string) error {
client, c, err := client(shutdown.Context(), s.cfg, pbHealth.NewHealthClient)
if err != nil {
return err
}
defer c.Close()

res, err := client.Check(shutdown.Context(), &pbHealth.HealthCheckRequest{})
if err != nil {
return err
}

switch s := res.GetStatus(); s {
case pbHealth.HealthCheckResponse_SERVING:
println("OK")
return nil
default:
return errors.Errorf("Not healthy: %s", s.String())
}
}
return nil
}
Loading

0 comments on commit 50f9f2d

Please sign in to comment.