add scoped role assignments to k8s operator (#65623)

* add scoped role assignments for k8s

Fix tests and retrieving the scoped role assignments by adding subkind

Add additional catch to treat comparefailed error as not found for scoped role assignments

* fix merge conflict
This commit is contained in:
williamong-tel
2026-04-22 19:51:46 +00:00
committed by GitHub
parent 368188489c
commit d1b32e62c5
15 changed files with 801 additions and 1 deletions
@@ -0,0 +1,44 @@
---
title: TeleportScopedRoleAssignmentV1
description: Provides a comprehensive list of fields in the TeleportScopedRoleAssignmentV1 resource available through the Teleport Kubernetes operator
---
{/*Auto-generated file. Do not edit.*/}
{/*To regenerate, navigate to integrations/operator and run "make crd-docs".*/}
{/* Disable the outdated name check since custom resource fields occasionally
need to refer to these. */}
{/* vale 3rd-party-products.former-names = NO */}
This guide is a comprehensive reference to the fields in the `TeleportScopedRoleAssignmentV1`
resource, which you can apply after installing the Teleport Kubernetes operator.
## resources.teleport.dev/v1
**apiVersion:** resources.teleport.dev/v1
|Field|Type|Description|
|---|---|---|
|apiVersion|string|APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources|
|kind|string|Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds|
|metadata|object||
|scope|string|Scope is the scope of the role assignment resource.|
|spec|[object](#spec)|ScopedRoleAssignment resource definition v1 from Teleport|
### spec
|Field|Type|Description|
|---|---|---|
|assignments|[][object](#specassignments-items)|Assignments is a list of individual role @ scope assignments.|
|bot_name|string|Name of the Bot to whom all contained assignments apply. Mutually exclusive with `user`.|
|bot_scope|string|Scope of the Bot to whom all contained assignments apply. Required if `bot_name` is set. If specified, assignment scopes must be equal or descendent of this scope.|
|user|string|User is the user to whom all contained assignments apply. Mutually exclusive with `bot_name`.|
### spec.assignments items
|Field|Type|Description|
|---|---|---|
|role|string|Roles is the name of the role that is assigned by this assignment.|
|scope|string|Scope is the scope to which the role is assigned. This must be a member/child of the scope of the [ScopedRoleAssignment] in which this assignment is contained.|
@@ -0,0 +1,146 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: teleportscopedroleassignmentsv1.resources.teleport.dev
spec:
group: resources.teleport.dev
names:
kind: TeleportScopedRoleAssignmentV1
listKind: TeleportScopedRoleAssignmentV1List
plural: teleportscopedroleassignmentsv1
shortNames:
- scopedroleassignmentv1
- scopedroleassignmentsv1
singular: teleportscopedroleassignmentv1
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: ScopedRoleAssignmentV1 is the Schema for the scopedroleassignmentsv1
API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
scope:
description: Scope is the scope of the role assignment resource.
type: string
spec:
description: ScopedRoleAssignment resource definition v1 from Teleport
properties:
assignments:
description: Assignments is a list of individual role @ scope assignments.
items:
properties:
role:
description: Roles is the name of the role that is assigned
by this assignment.
type: string
scope:
description: Scope is the scope to which the role is assigned.
This must be a member/child of the scope of the [ScopedRoleAssignment]
in which this assignment is contained.
type: string
type: object
nullable: true
type: array
bot_name:
description: Name of the Bot to whom all contained assignments apply.
Mutually exclusive with `user`.
type: string
bot_scope:
description: Scope of the Bot to whom all contained assignments apply.
Required if `bot_name` is set. If specified, assignment scopes must
be equal or descendent of this scope.
type: string
user:
description: User is the user to whom all contained assignments apply.
Mutually exclusive with `bot_name`.
type: string
type: object
status:
description: Status defines the observed state of the Teleport resource
properties:
conditions:
description: Conditions represent the latest available observations
of an object's state
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
teleportResourceID:
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: null
storedVersions: null
@@ -68,6 +68,8 @@ rules:
- teleportscopedtokensv1/status
- teleportscopedrolesv1
- teleportscopedrolesv1/status
- teleportscopedroleassignmentsv1
- teleportscopedroleassignmentsv1/status
verbs:
- get
- list
@@ -260,6 +260,14 @@ data:
- readnosecrets
- update
- delete
- resources:
- scoped_role_assignment
verbs:
- list
- create
- read
- update
- delete
deny: {}
version: v7
---
+2 -1
View File
@@ -96,7 +96,8 @@ PROTOS = \
teleport/summarizer/v1/summarizer.proto \
teleport/accessmonitoringrules/v1/access_monitoring_rules.proto \
teleport/scopes/joining/v1/token.proto \
teleport/scopes/access/v1/role.proto
teleport/scopes/access/v1/role.proto \
teleport/scopes/access/v1/assignment.proto
.PHONY: crd-manifests
crd-manifests: crdgen ## Generates CRD manifests using crdgen.
@@ -0,0 +1,107 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package v1
import (
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/integrations/operator/apis/resources/teleportcr"
"github.com/gravitational/teleport/lib/scopes/access"
)
func init() {
SchemeBuilder.Register(&TeleportScopedRoleAssignmentV1{})
}
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// TeleportScopedRoleAssignmentV1 represents a Kubernetes custom resource for
// Scoped Role Assignments.
type TeleportScopedRoleAssignmentV1 struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Scope string `json:"scope"`
Spec *TeleportScopedRoleAssignmentV1Spec `json:"spec,omitempty"`
Status teleportcr.Status `json:"status"`
}
// TeleportScopedRoleAssignmentV1Spec defines the desired state of the Scoped Role Assignment.
type TeleportScopedRoleAssignmentV1Spec accessv1.ScopedRoleAssignmentSpec
//+kubebuilder:object:root = true
// TeleportScopedRoleAssignmentV1List contains a list of [TeleportScopedRoleAssignmentV1]
// objects.
type TeleportScopedRoleAssignmentV1List struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []TeleportScopedRoleAssignmentV1 `json:"items"`
}
// ToTeleport returns a Teleport representation of this Kubernetes resource.
func (m *TeleportScopedRoleAssignmentV1) ToTeleport() *accessv1.ScopedRoleAssignment {
resource := &accessv1.ScopedRoleAssignment{
Kind: access.KindScopedRoleAssignment,
SubKind: access.SubKindDynamic,
Version: types.V1,
Metadata: &headerv1.Metadata{
Name: m.Name,
Description: m.Annotations[teleportcr.DescriptionKey],
Labels: m.Labels,
},
Scope: m.Scope,
Spec: (*accessv1.ScopedRoleAssignmentSpec)(m.Spec),
}
return resource
}
// StatusConditions returns a pointer to Status.Conditions slice. This is used
// by the Teleport resource controller to report conditions back to resource.
func (m *TeleportScopedRoleAssignmentV1) StatusConditions() *[]metav1.Condition {
return &m.Status.Conditions
}
// UnmarshalJSON delegates unmarshaling of the TeleportScopedRoleAssignmentV1Spec to
// protojson, which is necessary for Proto RFD153 resources to be unmarshaled
// correctly from the unstructured object.
func (spec *TeleportScopedRoleAssignmentV1Spec) UnmarshalJSON(data []byte) error {
return protojson.UnmarshalOptions{
DiscardUnknown: true,
}.Unmarshal(data, (*accessv1.ScopedRoleAssignmentSpec)(spec))
}
// MarshalJSON delegates marshaling of the TeleportScopedRoleAssignmentV1Spec to
// protojson, which is necessary for Proto RFD153 resources to be marshaled
// correctly into an unstructured object.
func (spec *TeleportScopedRoleAssignmentV1Spec) MarshalJSON() ([]byte, error) {
return protojson.MarshalOptions{
UseProtoNames: true,
}.Marshal((*accessv1.ScopedRoleAssignmentSpec)(spec))
}
// DeepCopyInto deep-copies one TeleportScopedRoleAssignmentV1Spec into another.
// Required to satisfy runtime.Object interface.
func (spec *TeleportScopedRoleAssignmentV1Spec) DeepCopyInto(out *TeleportScopedRoleAssignmentV1Spec) {
proto.Reset((*accessv1.ScopedRoleAssignmentSpec)(out))
proto.Merge((*accessv1.ScopedRoleAssignmentSpec)(out), (*accessv1.ScopedRoleAssignmentSpec)(spec))
}
@@ -1431,6 +1431,70 @@ func (in *TeleportSAMLIdPServiceProviderV1Spec) DeepCopy() *TeleportSAMLIdPServi
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeleportScopedRoleAssignmentV1) DeepCopyInto(out *TeleportScopedRoleAssignmentV1) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
if in.Spec != nil {
in, out := &in.Spec, &out.Spec
*out = (*in).DeepCopy()
}
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportScopedRoleAssignmentV1.
func (in *TeleportScopedRoleAssignmentV1) DeepCopy() *TeleportScopedRoleAssignmentV1 {
if in == nil {
return nil
}
out := new(TeleportScopedRoleAssignmentV1)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *TeleportScopedRoleAssignmentV1) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeleportScopedRoleAssignmentV1List) DeepCopyInto(out *TeleportScopedRoleAssignmentV1List) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]TeleportScopedRoleAssignmentV1, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportScopedRoleAssignmentV1List.
func (in *TeleportScopedRoleAssignmentV1List) DeepCopy() *TeleportScopedRoleAssignmentV1List {
if in == nil {
return nil
}
out := new(TeleportScopedRoleAssignmentV1List)
in.DeepCopyInto(out)
return out
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportScopedRoleAssignmentV1Spec.
func (in *TeleportScopedRoleAssignmentV1Spec) DeepCopy() *TeleportScopedRoleAssignmentV1Spec {
if in == nil {
return nil
}
out := new(TeleportScopedRoleAssignmentV1Spec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeleportScopedRoleV1) DeepCopyInto(out *TeleportScopedRoleV1) {
*out = *in
@@ -0,0 +1,146 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: teleportscopedroleassignmentsv1.resources.teleport.dev
spec:
group: resources.teleport.dev
names:
kind: TeleportScopedRoleAssignmentV1
listKind: TeleportScopedRoleAssignmentV1List
plural: teleportscopedroleassignmentsv1
shortNames:
- scopedroleassignmentv1
- scopedroleassignmentsv1
singular: teleportscopedroleassignmentv1
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: ScopedRoleAssignmentV1 is the Schema for the scopedroleassignmentsv1
API
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
scope:
description: Scope is the scope of the role assignment resource.
type: string
spec:
description: ScopedRoleAssignment resource definition v1 from Teleport
properties:
assignments:
description: Assignments is a list of individual role @ scope assignments.
items:
properties:
role:
description: Roles is the name of the role that is assigned
by this assignment.
type: string
scope:
description: Scope is the scope to which the role is assigned.
This must be a member/child of the scope of the [ScopedRoleAssignment]
in which this assignment is contained.
type: string
type: object
nullable: true
type: array
bot_name:
description: Name of the Bot to whom all contained assignments apply.
Mutually exclusive with `user`.
type: string
bot_scope:
description: Scope of the Bot to whom all contained assignments apply.
Required if `bot_name` is set. If specified, assignment scopes must
be equal or descendent of this scope.
type: string
user:
description: User is the user to whom all contained assignments apply.
Mutually exclusive with `bot_name`.
type: string
type: object
status:
description: Status defines the observed state of the Teleport resource
properties:
conditions:
description: Conditions represent the latest available observations
of an object's state
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
teleportResourceID:
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
plural: ""
conditions: null
storedVersions: null
@@ -0,0 +1,80 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"context"
"github.com/gravitational/trace"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gravitational/teleport/api/client"
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
"github.com/gravitational/teleport/integrations/operator/controllers"
"github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
)
type scopedRoleAssignmentClient struct {
teleportClient *client.Client
}
func (s *scopedRoleAssignmentClient) Create(ctx context.Context, assignment *accessv1.ScopedRoleAssignment) error {
_, err := s.teleportClient.ScopedAccessServiceClient().CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{
Assignment: assignment,
})
return trace.Wrap(err)
}
func (s *scopedRoleAssignmentClient) Delete(ctx context.Context, name string) error {
_, err := s.teleportClient.ScopedAccessServiceClient().DeleteScopedRoleAssignment(ctx, &accessv1.DeleteScopedRoleAssignmentRequest{
Name: name,
SubKind: scopedaccess.SubKindDynamic,
})
if err != nil && trace.IsCompareFailed(err) {
return trace.NotFound("scoped role assignment %q not found: %v", name, err)
}
return trace.Wrap(err)
}
func (s *scopedRoleAssignmentClient) Get(ctx context.Context, name string) (*accessv1.ScopedRoleAssignment, error) {
resp, err := s.teleportClient.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{
Name: name,
SubKind: scopedaccess.SubKindDynamic,
})
if err != nil {
return nil, trace.Wrap(err)
}
return resp.GetAssignment(), nil
}
func (s *scopedRoleAssignmentClient) Update(ctx context.Context, assignment *accessv1.ScopedRoleAssignment) error {
_, err := s.teleportClient.ScopedAccessServiceClient().UpdateScopedRoleAssignment(ctx, &accessv1.UpdateScopedRoleAssignmentRequest{
Assignment: assignment,
})
return trace.Wrap(err)
}
func NewScopedRoleAssignmentV1Reconciler(client kclient.Client, tClient *client.Client) (controllers.Reconciler, error) {
return reconcilers.NewTeleportResource153Reconciler[*accessv1.ScopedRoleAssignment, *resourcesv1.TeleportScopedRoleAssignmentV1](
client,
&scopedRoleAssignmentClient{
teleportClient: tClient,
},
)
}
@@ -0,0 +1,175 @@
/*
* Teleport
* Copyright (C) 2026 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package resources_test
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/uuid"
"github.com/gravitational/trace"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
"github.com/gravitational/teleport/api/types"
resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
"github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
"github.com/gravitational/teleport/integrations/operator/controllers/resources"
"github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
"github.com/gravitational/teleport/lib/scopes/access"
)
var scopedRoleAssignmentSpec = &accessv1.ScopedRoleAssignmentSpec{
User: "test-user",
Assignments: []*accessv1.Assignment{
{
Role: "test-role",
Scope: "/staging",
},
},
}
type scopedRoleAssignmentTestingPrimitives struct {
setup *testSetup
reconcilers.Resource153Adapter[*accessv1.ScopedRoleAssignment]
}
func (g *scopedRoleAssignmentTestingPrimitives) Init(setup *testSetup) {
g.setup = setup
}
func (g *scopedRoleAssignmentTestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
return nil
}
func (g *scopedRoleAssignmentTestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
assignment := &accessv1.ScopedRoleAssignment{
Kind: access.KindScopedRoleAssignment,
Version: types.V1,
SubKind: access.SubKindDynamic,
Metadata: &headerv1.Metadata{
Name: name,
Labels: map[string]string{
types.OriginLabel: types.OriginKubernetes,
},
},
Scope: "/staging",
Spec: scopedRoleAssignmentSpec,
}
_, err := g.setup.TeleportClient.ScopedAccessServiceClient().CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{
Assignment: assignment,
})
return trace.Wrap(err)
}
func (g *scopedRoleAssignmentTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (*accessv1.ScopedRoleAssignment, error) {
resp, err := g.setup.TeleportClient.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{
Name: name,
SubKind: access.SubKindDynamic,
})
if err != nil {
return nil, trace.Wrap(err)
}
return resp.GetAssignment(), nil
}
func (g *scopedRoleAssignmentTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
_, err := g.setup.TeleportClient.ScopedAccessServiceClient().DeleteScopedRoleAssignment(ctx, &accessv1.DeleteScopedRoleAssignmentRequest{
Name: name,
SubKind: access.SubKindDynamic,
})
return trace.Wrap(err)
}
func (g *scopedRoleAssignmentTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
assignment := &resourcesv1.TeleportScopedRoleAssignmentV1{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: g.setup.Namespace.Name,
},
Scope: "/staging",
Spec: (*resourcesv1.TeleportScopedRoleAssignmentV1Spec)(scopedRoleAssignmentSpec),
}
return trace.Wrap(g.setup.K8sClient.Create(ctx, assignment))
}
func (g *scopedRoleAssignmentTestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
assignment := &resourcesv1.TeleportScopedRoleAssignmentV1{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: g.setup.Namespace.Name,
},
}
return trace.Wrap(g.setup.K8sClient.Delete(ctx, assignment))
}
func (g *scopedRoleAssignmentTestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportScopedRoleAssignmentV1, error) {
assignment := &resourcesv1.TeleportScopedRoleAssignmentV1{}
obj := kclient.ObjectKey{
Name: name,
Namespace: g.setup.Namespace.Name,
}
err := g.setup.K8sClient.Get(ctx, obj, assignment)
return assignment, trace.Wrap(err)
}
func (g *scopedRoleAssignmentTestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
assignment, err := g.GetKubernetesResource(ctx, name)
if err != nil {
return trace.Wrap(err)
}
assignment.Spec.Assignments = []*accessv1.Assignment{
{
Role: "test-role",
Scope: "/staging/aa",
},
}
return trace.Wrap(g.setup.K8sClient.Update(ctx, assignment))
}
func (g *scopedRoleAssignmentTestingPrimitives) CompareTeleportAndKubernetesResource(
tResource *accessv1.ScopedRoleAssignment, kubeResource *resourcesv1.TeleportScopedRoleAssignmentV1) (bool, string) {
diff := cmp.Diff(
tResource,
kubeResource.ToTeleport(),
testlib.ProtoCompareOptions()...,
)
return diff == "", diff
}
func TestScopedRoleAssignmentCreation(t *testing.T) {
t.Setenv("TELEPORT_UNSTABLE_SCOPES", "yes")
test := &scopedRoleAssignmentTestingPrimitives{}
testlib.ResourceCreationSynchronousTest(t, resources.NewScopedRoleAssignmentV1Reconciler, test, testlib.WithResourceName(uuid.New().String()))
}
func TestScopedRoleAssignmentDeletionDrift(t *testing.T) {
t.Setenv("TELEPORT_UNSTABLE_SCOPES", "yes")
test := &scopedRoleAssignmentTestingPrimitives{}
testlib.ResourceDeletionDriftSynchronousTest(t, resources.NewScopedRoleAssignmentV1Reconciler, test, testlib.WithResourceName(uuid.New().String()))
}
func TestScopedRoleAssignmentUpdate(t *testing.T) {
t.Setenv("TELEPORT_UNSTABLE_SCOPES", "yes")
test := &scopedRoleAssignmentTestingPrimitives{}
testlib.ResourceUpdateTestSynchronous(t, resources.NewScopedRoleAssignmentV1Reconciler, test, testlib.WithResourceName(uuid.New().String()))
}
@@ -67,6 +67,7 @@ func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *c
{"TeleportSAMLIdPServiceProviderV1", NewSAMLIdPServiceProviderV1Reconciler},
{"TeleportScopedTokenV1", NewScopedTokenV1Reconciler},
{"TeleportScopedRoleV1", NewScopedRoleV1Reconciler},
{"TeleportScopedRoleAssignmentV1", NewScopedRoleAssignmentV1Reconciler},
}
oidc := modules.GetProtoEntitlement(features, entitlements.OIDC)
@@ -142,6 +142,7 @@ func defaultTeleportServiceConfig(t *testing.T, insecureMode bool) (*helpers.Tel
types.NewRule(types.KindSAMLIdPServiceProvider, unrestricted),
types.NewRule(types.KindScopedToken, unrestricted),
types.NewRule(access.KindScopedRole, unrestricted),
types.NewRule(access.KindScopedRoleAssignment, unrestricted),
},
},
})
@@ -281,6 +281,13 @@ func generateSchema(file *File, groupName string, format crdFormatFunc, resp *go
withAdditionalRootFields([]string{"scope"}),
},
},
{
name: "ScopedRoleAssignment",
opts: []resourceSchemaOption{
withVersionOverride(types.V1),
withAdditionalRootFields([]string{"scope"}),
},
},
}
for _, resource := range resources {
@@ -0,0 +1,10 @@
apiVersion: resources.teleport.dev/v1
kind: TeleportScopedRoleAssignmentV1
scope: /foo
sub_kind: dynamic
spec:
user: "test"
assignments:
- role: some-role
scope: /foo/bar
version: v1
@@ -197,6 +197,14 @@ spec:
- readnosecrets
- update
- delete
- resources:
- scoped_role_assignment
verbs:
- list
- create
- read
- update
- delete
deny: {}
version: v7