Skip to content
This repository has been archived by the owner on Apr 22, 2024. It is now read-only.

Commit

Permalink
Added in-memory session store
Browse files Browse the repository at this point in the history
  • Loading branch information
nacx committed Feb 12, 2024
1 parent d9be6fe commit 814ccc5
Show file tree
Hide file tree
Showing 14 changed files with 630 additions and 55 deletions.
27 changes: 27 additions & 0 deletions internal/authz/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2024 Tetrate
//
// 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.

package authz

import (
"context"

envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
)

// Authz is an interface for handling authorization requests.
type Authz interface {
// Process a CheckRequest and populate a CheckResponse.
Process(ctx context.Context, req *envoy.CheckRequest, resp *envoy.CheckResponse) error
}
57 changes: 57 additions & 0 deletions internal/authz/mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024 Tetrate
//
// 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.

package authz

import (
"context"

envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/tetratelabs/telemetry"
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"

mockv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/mock"
"github.com/tetrateio/authservice-go/internal"
)

var _ Authz = (*mockHandler)(nil)

// mockHandler handler is an implementation of the Authz interface.
type mockHandler struct {
log telemetry.Logger
config *mockv1.MockConfig
}

// NewMockHandler creates a new Mock implementation of the Authz interface.
func NewMockHandler(cfg *mockv1.MockConfig) Authz {
return &mockHandler{
log: internal.Logger(internal.Authz).With("type", "mockHandler"),
config: cfg,
}
}

// Process a CheckRequest and populate a CheckResponse according to the mockHandler configuration.
func (m *mockHandler) Process(ctx context.Context, _ *envoy.CheckRequest, resp *envoy.CheckResponse) error {
log := m.log.Context(ctx)

code := codes.PermissionDenied
if m.config.GetAllow() {
code = codes.OK
}

log.Debug("process", "status", code.String())
resp.Status = &status.Status{Code: int32(code)}
return nil
}
51 changes: 51 additions & 0 deletions internal/authz/mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 Tetrate
//
// 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.

package authz

import (
"context"
"testing"

envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/stretchr/testify/require"
"github.com/tetratelabs/telemetry"
"google.golang.org/grpc/codes"

mockv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/mock"
)

func TestProcessMock(t *testing.T) {
tests := []struct {
name string
allow bool
want codes.Code
}{
{"allow", true, codes.OK},
{"deny", false, codes.PermissionDenied},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var (
m = &mockHandler{log: telemetry.NoopLogger(), config: &mockv1.MockConfig{Allow: tt.allow}}
req = &envoy.CheckRequest{}
resp = &envoy.CheckResponse{}
)
err := m.Process(context.Background(), req, resp)
require.NoError(t, err)
require.Equal(t, int32(tt.want), resp.Status.Code)
})
}
}
50 changes: 50 additions & 0 deletions internal/authz/oidc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2024 Tetrate
//
// 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.

package authz

import (
"context"

envoy "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/tetratelabs/telemetry"

oidcv1 "github.com/tetrateio/authservice-go/config/gen/go/v1/oidc"
"github.com/tetrateio/authservice-go/internal"
"github.com/tetrateio/authservice-go/internal/authz/oidc"
)

var _ Authz = (*oidcHandler)(nil)

// oidc handler is a mockHandler implementation of the Authz interface that implements
// the OpenID connect protocol.
type oidcHandler struct {
log telemetry.Logger
config *oidcv1.OIDCConfig
store oidc.SessionStore
}

// NewOIDCHandler creates a new OIDC implementation of the Authz interface.
func NewOIDCHandler(cfg *oidcv1.OIDCConfig, store oidc.SessionStore) Authz {
return &oidcHandler{
log: internal.Logger(internal.Authz).With("type", "oidc"),
config: cfg,
store: store,
}
}

// Process a CheckRequest and populate a CheckResponse according to the mockHandler configuration.
func (o *oidcHandler) Process(_ context.Context, _ *envoy.CheckRequest, _ *envoy.CheckResponse) error {
return nil
}
156 changes: 156 additions & 0 deletions internal/authz/oidc/memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2024 Tetrate
//
// 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.

package oidc

import (
"sync"
"time"

"github.com/tetratelabs/telemetry"

"github.com/tetrateio/authservice-go/internal"
)

var _ SessionStore = (*memoryStore)(nil)

// memoryStore is an in-memory implementation of the SessionStore interface.
type memoryStore struct {
log telemetry.Logger
clock Clock
absoluteSessionTimeout time.Duration
idleSessionTimeout time.Duration

mu sync.Mutex
sessions map[string]*session
}

// NewMemoryStore creates a new in-memory session store.
func NewMemoryStore(clock Clock, absoluteSessionTimeout, idleSessionTimeout time.Duration) SessionStore {
return &memoryStore{
log: internal.Logger(internal.Session).With("type", "memory"),
clock: clock,
absoluteSessionTimeout: absoluteSessionTimeout,
idleSessionTimeout: idleSessionTimeout,
sessions: make(map[string]*session),
}
}

func (m *memoryStore) SetTokenResponse(sessionID string, tokenResponse *TokenResponse) {
m.set(sessionID, func(s *session) {
s.tokenResponse = tokenResponse
})
}

func (m *memoryStore) GetTokenResponse(sessionID string) *TokenResponse {
m.mu.Lock()
defer m.mu.Unlock()

s := m.sessions[sessionID]
if s == nil {
return nil
}

s.accessed = m.clock.Now()
return s.tokenResponse
}

func (m *memoryStore) SetAuthorizationState(sessionID string, authorizationState *AuthorizationState) {
m.set(sessionID, func(s *session) {
s.authorizationState = authorizationState
})
}

func (m *memoryStore) GetAuthorizationState(sessionID string) *AuthorizationState {
m.mu.Lock()
defer m.mu.Unlock()

s := m.sessions[sessionID]
if s == nil {
return nil
}

s.accessed = m.clock.Now()
return s.authorizationState
}

func (m *memoryStore) ClearAuthorizationState(sessionID string) {
m.mu.Lock()
defer m.mu.Unlock()

if s := m.sessions[sessionID]; s != nil {
s.accessed = m.clock.Now()
s.authorizationState = nil
}
}

func (m *memoryStore) RemoveSession(sessionID string) {
m.mu.Lock()
defer m.mu.Unlock()

delete(m.sessions, sessionID)
}

func (m *memoryStore) RemoveAllExpired() {
var (
earliestTimeAddedToKeep = m.clock.Now().Add(-m.absoluteSessionTimeout)
earliestTimeIdleToKeep = m.clock.Now().Add(-m.idleSessionTimeout)
shouldCheckAbsoluteTimeout = m.absoluteSessionTimeout > 0
shouldCheckIdleTimeout = m.idleSessionTimeout > 0
)

m.mu.Lock()
defer m.mu.Unlock()

for sessionID, s := range m.sessions {
expiredBasedOnTimeAdded := shouldCheckAbsoluteTimeout && s.added.Before(earliestTimeAddedToKeep)
expiredBasedOnIdleTime := shouldCheckIdleTimeout && s.accessed.Before(earliestTimeIdleToKeep)

if expiredBasedOnTimeAdded || expiredBasedOnIdleTime {
delete(m.sessions, sessionID)
}
}
}

// set the given session with the given setter function and record the access time.
func (m *memoryStore) set(sessionID string, setter func(s *session)) {
m.mu.Lock()
defer m.mu.Unlock()

s := m.sessions[sessionID]
if s != nil {
s.accessed = m.clock.Now()
setter(s)
} else {
s = newSession(m.clock.Now())
setter(s)
m.sessions[sessionID] = s
}
}

// session holds the data of a session stored int he in-memory cache
type session struct {
tokenResponse *TokenResponse
authorizationState *AuthorizationState
added time.Time
accessed time.Time
}

// newSession creates a new session with the given creation time.
func newSession(t time.Time) *session {
return &session{
added: t,
accessed: t,
}
}
Loading

0 comments on commit 814ccc5

Please sign in to comment.