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

Added in-memory session store #4

Merged
merged 1 commit into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// oidc handler is a mockHandler implementation of the Authz interface that implements
// oidc handler is an 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// session holds the data of a session stored int he in-memory cache
// session holds the data of a session stored in the 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
Loading