Skip to content
Draft
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
439 changes: 439 additions & 0 deletions go/api/gen/kagent/api/v1alpha1/authorization.pb.go

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions go/api/gen/kagent/api/v1alpha1/authorization_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions go/core/internal/grpcserver/authorization.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package grpcserver

import (
"context"

apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1"
"github.com/kagent-dev/kagent/go/core/internal/service/kubeauth"
"github.com/kagent-dev/kagent/go/core/pkg/auth"
)

type authorizationServer struct {
apiv1alpha1.UnimplementedAuthorizationServiceServer
reviewer *kubeauth.AccessReviewer
}

func (s *authorizationServer) CheckAccess(ctx context.Context, request *apiv1alpha1.CheckAccessRequest) (*apiv1alpha1.CheckAccessResponse, error) {
resourceTypes := map[apiv1alpha1.AuthorizationResourceType]string{
apiv1alpha1.AuthorizationResourceType_AUTHORIZATION_RESOURCE_TYPE_AGENT_TEMPLATE: auth.ResourceAgentTemplate,
apiv1alpha1.AuthorizationResourceType_AUTHORIZATION_RESOURCE_TYPE_HARNESS: auth.ResourceHarness,
apiv1alpha1.AuthorizationResourceType_AUTHORIZATION_RESOURCE_TYPE_MODEL_CONFIG: auth.ResourceModelConfig,
}
verbs := map[apiv1alpha1.AuthorizationVerb]auth.Verb{
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_GET: auth.VerbGet,
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_CREATE: auth.VerbCreate,
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_UPDATE: auth.VerbUpdate,
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_DELETE: auth.VerbDelete,
}
authorizationVerbs := map[auth.Verb]apiv1alpha1.AuthorizationVerb{
auth.VerbGet: apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_GET,
auth.VerbCreate: apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_CREATE,
auth.VerbUpdate: apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_UPDATE,
auth.VerbDelete: apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_DELETE,
}
requestVerbs := make([]auth.Verb, len(request.GetVerbs()))
for i, verb := range request.GetVerbs() {
requestVerbs[i] = verbs[verb]
}
requestTargets := make([]kubeauth.ReviewTarget, len(request.GetTargets()))
for i, target := range request.GetTargets() {
requestTargets[i] = kubeauth.ReviewTarget{Namespace: target.GetNamespace(), Name: target.GetName()}
}

results, err := s.reviewer.Review(ctx, resourceTypes[request.GetResourceType()], requestVerbs, requestTargets)
if err != nil {
return nil, err
}

response := &apiv1alpha1.CheckAccessResponse{Results: make([]*apiv1alpha1.ResourceAccess, len(results))}
for i, result := range results {
target := &apiv1alpha1.AccessTarget{Namespace: result.Target.Namespace}
if result.Target.Name != "" {
name := result.Target.Name
target.Name = &name
}
allowedVerbs := make([]apiv1alpha1.AuthorizationVerb, len(result.AllowedVerbs))
for j, verb := range result.AllowedVerbs {
allowedVerbs[j] = authorizationVerbs[verb]
}
response.Results[i] = &apiv1alpha1.ResourceAccess{Target: target, AllowedVerbs: allowedVerbs}
}
return response, nil
}
104 changes: 104 additions & 0 deletions go/core/internal/grpcserver/authorization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package grpcserver

import (
"context"
"net"
"testing"

apiauthorization "github.com/kagent-dev/kagent/go/api/authorization"
apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1"
authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth"
"github.com/kagent-dev/kagent/go/core/internal/service/kubeauth"
pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/proto"
)

type accessReviewScopeCall struct {
verb pkgauth.Verb
resourceType string
}

type accessReviewAuthorizer struct {
scopeCalls []accessReviewScopeCall
}

func (*accessReviewAuthorizer) Check(context.Context, pkgauth.Principal, pkgauth.Verb, pkgauth.Resource) error {
return nil
}

func (a *accessReviewAuthorizer) Scope(_ context.Context, _ pkgauth.Principal, verb pkgauth.Verb, resourceType string) (apiauthorization.AuthorizationScope, error) {
a.scopeCalls = append(a.scopeCalls, accessReviewScopeCall{verb: verb, resourceType: resourceType})
return apiauthorization.AuthorizationScope{Kind: apiauthorization.ScopeAll}, nil
}

func TestAuthorizationServiceGeneratedClient(t *testing.T) {
authorizer := &accessReviewAuthorizer{}
listener := bufconn.Listen(DefaultMaxMessageSize)
server, err := New(Config{
Listener: listener,
Registerer: prometheus.NewRegistry(),
Authenticator: &authimpl.UnsecureAuthenticator{},
SystemService: testSystemService(),
AuthorizationService: kubeauth.NewAccessReviewer(authorizer),
})
require.NoError(t, err)
serverContext, cancelServer := context.WithCancel(t.Context())
done := make(chan error, 1)
go func() { done <- server.Start(serverContext) }()
t.Cleanup(func() {
cancelServer()
assert.NoError(t, <-done)
})

connection, err := grpc.NewClient(
"passthrough:///bufnet",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }),
)
require.NoError(t, err)
t.Cleanup(func() { _ = connection.Close() })
client := apiv1alpha1.NewAuthorizationServiceClient(connection)
name := "assistant"

response, err := client.CheckAccess(t.Context(), &apiv1alpha1.CheckAccessRequest{
ResourceType: apiv1alpha1.AuthorizationResourceType_AUTHORIZATION_RESOURCE_TYPE_AGENT_TEMPLATE,
Verbs: []apiv1alpha1.AuthorizationVerb{
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_UPDATE,
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_CREATE,
},
Targets: []*apiv1alpha1.AccessTarget{
{Namespace: "team-a", Name: &name},
{Namespace: "team-b"},
},
})
require.NoError(t, err)
want := &apiv1alpha1.CheckAccessResponse{
Results: []*apiv1alpha1.ResourceAccess{
{
Target: &apiv1alpha1.AccessTarget{Namespace: "team-a", Name: &name},
AllowedVerbs: []apiv1alpha1.AuthorizationVerb{
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_UPDATE,
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_CREATE,
},
},
{
Target: &apiv1alpha1.AccessTarget{Namespace: "team-b"},
AllowedVerbs: []apiv1alpha1.AuthorizationVerb{
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_UPDATE,
apiv1alpha1.AuthorizationVerb_AUTHORIZATION_VERB_CREATE,
},
},
},
}
assert.True(t, proto.Equal(want, response), "response = %v, want %v", response, want)
assert.Equal(t, []accessReviewScopeCall{
{verb: pkgauth.VerbUpdate, resourceType: pkgauth.ResourceAgentTemplate},
{verb: pkgauth.VerbCreate, resourceType: pkgauth.ResourceAgentTemplate},
}, authorizer.scopeCalls)
}
1 change: 1 addition & 0 deletions go/core/internal/grpcserver/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func DefaultMethodPolicies() MethodPolicies {
apiv1alpha1.HarnessService_ListHarnesses_FullMethodName: auth.AccessRead,
apiv1alpha1.HarnessService_CreateHarness_FullMethodName: auth.AccessCreate,
apiv1alpha1.HarnessService_DeleteHarness_FullMethodName: auth.AccessDelete,
apiv1alpha1.AuthorizationService_CheckAccess_FullMethodName: auth.AccessRead,
}
policies[apiv1alpha1.AgentInstanceService_CreateAgentInstance_FullMethodName] = auth.AccessCreate
policies[apiv1alpha1.AgentInstanceService_GetAgentInstance_FullMethodName] = auth.AccessRead
Expand Down
6 changes: 6 additions & 0 deletions go/core/internal/grpcserver/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func TestAgentInstanceServicePoliciesMatchTheirEffect(t *testing.T) {
}
}

func TestAuthorizationServicePolicyIsRead(t *testing.T) {
if got := DefaultMethodPolicies()[apiv1alpha1.AuthorizationService_CheckAccess_FullMethodName]; got != pkgauth.AccessRead {
t.Fatalf("CheckAccess policy = %q, want %q", got, pkgauth.AccessRead)
}
}

// TestReadOnlyShareCannotRenameAConversation is the property the policy entry
// exists for, measured through the interceptor rather than read off the table: a
// read-only share link may open a conversation and must not be able to retitle
Expand Down
Loading
Loading