diff --git a/docs/pages/reference/operator-resources/resources-teleport-dev-appsv3.mdx b/docs/pages/reference/operator-resources/resources-teleport-dev-appsv3.mdx
new file mode 100644
index 0000000000000..da69fb018cabf
--- /dev/null
+++ b/docs/pages/reference/operator-resources/resources-teleport-dev-appsv3.mdx
@@ -0,0 +1,120 @@
+---
+title: TeleportAppV3
+description: Provides a comprehensive list of fields in the TeleportAppV3 resource available through the Teleport Kubernetes operator
+tocDepth: 3
+---
+
+{/*Auto-generated file. Do not edit.*/}
+{/*To regenerate, navigate to integrations/operator and run "make crd-docs".*/}
+
+This guide is a comprehensive reference to the fields in the `TeleportAppV3`
+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||
+|spec|[object](#spec)|App resource definition v3 from Teleport|
+
+### spec
+
+|Field|Type|Description|
+|---|---|---|
+|UserGroups|[]string|UserGroups are a list of user group IDs that this app is associated with.|
+|aws|[object](#specaws)|AWS contains additional options for AWS applications.|
+|cloud|string|Cloud identifies the cloud instance the app represents.|
+|cors|[object](#speccors)|CORSPolicy defines the Cross-Origin Resource Sharing settings for the app.|
+|dynamic_labels|[object](#specdynamic_labels)|DynamicLabels are the app's command labels.|
+|identity_center|[object](#specidentity_center)|IdentityCenter encasulates AWS identity-center specific information. Only valid for Identity Center account apps.|
+|insecure_skip_verify|boolean|InsecureSkipVerify disables app's TLS certificate verification.|
+|integration|string|Integration is the integration name that must be used to access this Application. Only applicable to AWS App Access. If present, the Application must use the Integration's credentials instead of ambient credentials to access Cloud APIs.|
+|public_addr|string|PublicAddr is the public address the application is accessible at.|
+|required_app_names|[]string|RequiredAppNames is a list of app names that are required for this app to function. Any app listed here will be part of the authentication redirect flow and authenticate along side this app.|
+|rewrite|[object](#specrewrite)|Rewrite is a list of rewriting rules to apply to requests and responses.|
+|tcp_ports|[][object](#spectcp_ports-items)|TCPPorts is a list of ports and port ranges that an app agent can forward connections to. Only applicable to TCP App Access. If this field is not empty, URI is expected to contain no port number and start with the tcp protocol.|
+|uri|string|URI is the web app endpoint.|
+|use_any_proxy_public_addr|boolean|UseAnyProxyPublicAddr will rebuild this app's fqdn based on the proxy public addr that the request originated from. This should be true if your proxy has multiple proxy public addrs and you want the app to be accessible from any of them. If `public_addr` is explicitly set in the app spec, setting this value to true will overwrite that public address in the web UI.|
+
+### spec.aws
+
+|Field|Type|Description|
+|---|---|---|
+|external_id|string|ExternalID is the AWS External ID used when assuming roles in this app.|
+|roles_anywhere_profile|[object](#specawsroles_anywhere_profile)|RolesAnywhereProfile contains the IAM Roles Anywhere fields associated with this Application. These fields are set when performing the synchronization of AWS IAM Roles Anywhere Profiles into Teleport Apps.|
+
+### spec.aws.roles_anywhere_profile
+
+|Field|Type|Description|
+|---|---|---|
+|accept_role_session_name|boolean|Whether this Roles Anywhere Profile accepts a custom role session name. When not supported, the AWS Session Name will be the X.509 certificate's serial number. When supported, the AWS Session Name will be the identity's username. This values comes from: https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ProfileDetail.html / acceptRoleSessionName|
+|profile_arn|string|ProfileARN is the AWS IAM Roles Anywhere Profile ARN that originated this Teleport App.|
+
+### spec.cors
+
+|Field|Type|Description|
+|---|---|---|
+|allow_credentials|boolean|allow_credentials indicates whether credentials are allowed.|
+|allowed_headers|[]string|allowed_headers specifies which headers can be used when accessing the app.|
+|allowed_methods|[]string|allowed_methods specifies which methods are allowed when accessing the app.|
+|allowed_origins|[]string|allowed_origins specifies which origins are allowed to access the app.|
+|exposed_headers|[]string|exposed_headers indicates which headers are made available to scripts via the browser.|
+|max_age|integer|max_age indicates how long (in seconds) the results of a preflight request can be cached.|
+
+### spec.dynamic_labels
+
+|Field|Type|Description|
+|---|---|---|
+|key|string||
+|value|[object](#specdynamic_labelsvalue)||
+
+### spec.dynamic_labels.value
+
+|Field|Type|Description|
+|---|---|---|
+|command|[]string|Command is a command to run|
+|period|string|Period is a time between command runs|
+|result|string|Result captures standard output|
+
+### spec.identity_center
+
+|Field|Type|Description|
+|---|---|---|
+|account_id|string|Account ID is the AWS-assigned ID of the account|
+|permission_sets|[][object](#specidentity_centerpermission_sets-items)|PermissionSets lists the available permission sets on the given account|
+
+### spec.identity_center.permission_sets items
+
+|Field|Type|Description|
+|---|---|---|
+|arn|string|ARN is the fully-formed ARN of the Permission Set.|
+|assignment_name|string|AssignmentID is the ID of the Teleport Account Assignment resource that represents this permission being assigned on the enclosing Account.|
+|name|string|Name is the human-readable name of the Permission Set.|
+
+### spec.rewrite
+
+|Field|Type|Description|
+|---|---|---|
+|headers|[][object](#specrewriteheaders-items)|Headers is a list of headers to inject when passing the request over to the application.|
+|jwt_claims|string|JWTClaims configures whether roles/traits are included in the JWT token.|
+|redirect|[]string|Redirect defines a list of hosts which will be rewritten to the public address of the application if they occur in the "Location" header.|
+
+### spec.rewrite.headers items
+
+|Field|Type|Description|
+|---|---|---|
+|name|string|Name is the http header name.|
+|value|string|Value is the http header value.|
+
+### spec.tcp_ports items
+
+|Field|Type|Description|
+|---|---|---|
+|end_port|integer|EndPort describes the end of the range, inclusive. If set, it must be between 2 and 65535 and be greater than Port when describing a port range. When omitted or set to zero, it signifies that the port range defines a single port.|
+|port|integer|Port describes the start of the range. It must be between 1 and 65535.|
+
diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_appsv3.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_appsv3.yaml
new file mode 100644
index 0000000000000..9733ab327d7b8
--- /dev/null
+++ b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_appsv3.yaml
@@ -0,0 +1,335 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportappsv3.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportAppV3
+ listKind: TeleportAppV3List
+ plural: teleportappsv3
+ shortNames:
+ - appv3
+ - appsv3
+ singular: teleportappv3
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: AppV3 is the Schema for the appsv3 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
+ spec:
+ description: App resource definition v3 from Teleport
+ properties:
+ UserGroups:
+ description: UserGroups are a list of user group IDs that this app
+ is associated with.
+ items:
+ type: string
+ nullable: true
+ type: array
+ aws:
+ description: AWS contains additional options for AWS applications.
+ nullable: true
+ properties:
+ external_id:
+ description: ExternalID is the AWS External ID used when assuming
+ roles in this app.
+ type: string
+ roles_anywhere_profile:
+ description: RolesAnywhereProfile contains the IAM Roles Anywhere
+ fields associated with this Application. These fields are set
+ when performing the synchronization of AWS IAM Roles Anywhere
+ Profiles into Teleport Apps.
+ nullable: true
+ properties:
+ accept_role_session_name:
+ description: 'Whether this Roles Anywhere Profile accepts
+ a custom role session name. When not supported, the AWS
+ Session Name will be the X.509 certificate''s serial number.
+ When supported, the AWS Session Name will be the identity''s
+ username. This values comes from: https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ProfileDetail.html
+ / acceptRoleSessionName'
+ type: boolean
+ profile_arn:
+ description: ProfileARN is the AWS IAM Roles Anywhere Profile
+ ARN that originated this Teleport App.
+ type: string
+ type: object
+ type: object
+ cloud:
+ description: Cloud identifies the cloud instance the app represents.
+ type: string
+ cors:
+ description: CORSPolicy defines the Cross-Origin Resource Sharing
+ settings for the app.
+ nullable: true
+ properties:
+ allow_credentials:
+ description: allow_credentials indicates whether credentials are
+ allowed.
+ type: boolean
+ allowed_headers:
+ description: allowed_headers specifies which headers can be used
+ when accessing the app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ allowed_methods:
+ description: allowed_methods specifies which methods are allowed
+ when accessing the app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ allowed_origins:
+ description: allowed_origins specifies which origins are allowed
+ to access the app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ exposed_headers:
+ description: exposed_headers indicates which headers are made
+ available to scripts via the browser.
+ items:
+ type: string
+ nullable: true
+ type: array
+ max_age:
+ description: max_age indicates how long (in seconds) the results
+ of a preflight request can be cached.
+ format: int32
+ type: integer
+ type: object
+ dynamic_labels:
+ description: DynamicLabels are the app's command labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ identity_center:
+ description: IdentityCenter encasulates AWS identity-center specific
+ information. Only valid for Identity Center account apps.
+ nullable: true
+ properties:
+ account_id:
+ description: Account ID is the AWS-assigned ID of the account
+ type: string
+ permission_sets:
+ description: PermissionSets lists the available permission sets
+ on the given account
+ items:
+ properties:
+ arn:
+ description: ARN is the fully-formed ARN of the Permission
+ Set.
+ type: string
+ assignment_name:
+ description: AssignmentID is the ID of the Teleport Account
+ Assignment resource that represents this permission being
+ assigned on the enclosing Account.
+ type: string
+ name:
+ description: Name is the human-readable name of the Permission
+ Set.
+ type: string
+ type: object
+ nullable: true
+ type: array
+ type: object
+ insecure_skip_verify:
+ description: InsecureSkipVerify disables app's TLS certificate verification.
+ type: boolean
+ integration:
+ description: Integration is the integration name that must be used
+ to access this Application. Only applicable to AWS App Access. If
+ present, the Application must use the Integration's credentials
+ instead of ambient credentials to access Cloud APIs.
+ type: string
+ public_addr:
+ description: PublicAddr is the public address the application is accessible
+ at.
+ type: string
+ required_app_names:
+ description: RequiredAppNames is a list of app names that are required
+ for this app to function. Any app listed here will be part of the
+ authentication redirect flow and authenticate along side this app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ rewrite:
+ description: Rewrite is a list of rewriting rules to apply to requests
+ and responses.
+ nullable: true
+ properties:
+ headers:
+ description: Headers is a list of headers to inject when passing
+ the request over to the application.
+ items:
+ properties:
+ name:
+ description: Name is the http header name.
+ type: string
+ value:
+ description: Value is the http header value.
+ type: string
+ type: object
+ nullable: true
+ type: array
+ jwt_claims:
+ description: JWTClaims configures whether roles/traits are included
+ in the JWT token.
+ type: string
+ redirect:
+ description: Redirect defines a list of hosts which will be rewritten
+ to the public address of the application if they occur in the
+ "Location" header.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ tcp_ports:
+ description: TCPPorts is a list of ports and port ranges that an app
+ agent can forward connections to. Only applicable to TCP App Access.
+ If this field is not empty, URI is expected to contain no port number
+ and start with the tcp protocol.
+ items:
+ properties:
+ end_port:
+ description: EndPort describes the end of the range, inclusive.
+ If set, it must be between 2 and 65535 and be greater than
+ Port when describing a port range. When omitted or set to
+ zero, it signifies that the port range defines a single port.
+ format: int32
+ type: integer
+ port:
+ description: Port describes the start of the range. It must
+ be between 1 and 65535.
+ format: int32
+ type: integer
+ type: object
+ nullable: true
+ type: array
+ uri:
+ description: URI is the web app endpoint.
+ type: string
+ use_any_proxy_public_addr:
+ description: UseAnyProxyPublicAddr will rebuild this app's fqdn based
+ on the proxy public addr that the request originated from. This
+ should be true if your proxy has multiple proxy public addrs and
+ you want the app to be accessible from any of them. If `public_addr`
+ is explicitly set in the app spec, setting this value to true will
+ overwrite that public address in the web UI.
+ type: boolean
+ 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
diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml
index e6f073c7c0afa..bc6197b083da4 100644
--- a/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml
+++ b/examples/chart/teleport-cluster/charts/teleport-operator/templates/role.yaml
@@ -42,6 +42,8 @@ rules:
- teleportbotsv1/status
- teleportworkloadidentitiesv1
- teleportworkloadidentitiesv1/status
+ - teleportappsv3
+ - teleportappsv3/status
verbs:
- get
- list
diff --git a/examples/chart/teleport-cluster/templates/auth/config.yaml b/examples/chart/teleport-cluster/templates/auth/config.yaml
index 355bf05b8db7d..18f5a5f36e76a 100644
--- a/examples/chart/teleport-cluster/templates/auth/config.yaml
+++ b/examples/chart/teleport-cluster/templates/auth/config.yaml
@@ -66,6 +66,13 @@ data:
name: operator
spec:
allow:
+ # The operator role can see all nodes.
+ # This is required to reconcile OpenSSH and OpenSSHEICE nodes.
+ # However, it has no login set, so it cannot SSH on them
+ node_labels:
+ "*": ["*"]
+ app_labels:
+ "*": ["*"]
rules:
- resources:
- role
@@ -155,6 +162,14 @@ data:
- read
- update
- delete
+ - resources:
+ - app
+ verbs:
+ - list
+ - create
+ - read
+ - update
+ - delete
- resources:
- autoupdate_version
verbs:
diff --git a/integrations/operator/apis/resources/v1/appv3_types.go b/integrations/operator/apis/resources/v1/appv3_types.go
new file mode 100644
index 0000000000000..f6782f00029fc
--- /dev/null
+++ b/integrations/operator/apis/resources/v1/appv3_types.go
@@ -0,0 +1,95 @@
+/*
+ * Teleport
+ * Copyright (C) 2023 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 .
+ */
+
+package v1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/integrations/operator/apis/resources"
+)
+
+func init() {
+ SchemeBuilder.Register(&TeleportAppV3{}, &TeleportAppV3List{})
+}
+
+// TeleportAppV3Spec defines the desired state of TeleportAppV3
+type TeleportAppV3Spec types.AppSpecV3
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// TeleportAppV3 is the Schema for the roles API
+type TeleportAppV3 struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec TeleportAppV3Spec `json:"spec,omitempty"`
+ Status resources.Status `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// TeleportAppV3List contains a list of TeleportAppV3
+type TeleportAppV3List struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []TeleportAppV3 `json:"items"`
+}
+
+func (r TeleportAppV3) ToTeleport() types.Application {
+ return &types.AppV3{
+ Kind: types.KindApp,
+ Version: types.V3,
+ Metadata: types.Metadata{
+ Name: r.Name,
+ Labels: r.Labels,
+ Description: r.Annotations[resources.DescriptionKey],
+ },
+ Spec: types.AppSpecV3(r.Spec),
+ }
+}
+
+// Marshal serializes a spec into binary data.
+func (spec *TeleportAppV3Spec) Marshal() ([]byte, error) {
+ return (*types.AppSpecV3)(spec).Marshal()
+}
+
+// Unmarshal deserializes a spec from binary data.
+func (spec *TeleportAppV3Spec) Unmarshal(data []byte) error {
+ return (*types.AppSpecV3)(spec).Unmarshal(data)
+}
+
+// DeepCopyInto deep-copies one role spec into another.
+// Required to satisfy runtime.Object interface.
+func (spec *TeleportAppV3Spec) DeepCopyInto(out *TeleportAppV3Spec) {
+ data, err := spec.Marshal()
+ if err != nil {
+ panic(err)
+ }
+ *out = TeleportAppV3Spec{}
+ if err = out.Unmarshal(data); err != nil {
+ panic(err)
+ }
+}
+
+// StatusConditions returns a pointer to Status.Conditions slice.
+func (r *TeleportAppV3) StatusConditions() *[]metav1.Condition {
+ return &r.Status.Conditions
+}
diff --git a/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go b/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go
index cb03975acd04b..a63d74df27aaa 100644
--- a/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go
+++ b/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go
@@ -95,6 +95,75 @@ func (in *TeleportAccessListSpec) DeepCopy() *TeleportAccessListSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TeleportAppV3) DeepCopyInto(out *TeleportAppV3) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportAppV3.
+func (in *TeleportAppV3) DeepCopy() *TeleportAppV3 {
+ if in == nil {
+ return nil
+ }
+ out := new(TeleportAppV3)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TeleportAppV3) 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 *TeleportAppV3List) DeepCopyInto(out *TeleportAppV3List) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]TeleportAppV3, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportAppV3List.
+func (in *TeleportAppV3List) DeepCopy() *TeleportAppV3List {
+ if in == nil {
+ return nil
+ }
+ out := new(TeleportAppV3List)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TeleportAppV3List) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportAppV3Spec.
+func (in *TeleportAppV3Spec) DeepCopy() *TeleportAppV3Spec {
+ if in == nil {
+ return nil
+ }
+ out := new(TeleportAppV3Spec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeleportAutoupdateConfigV1) DeepCopyInto(out *TeleportAutoupdateConfigV1) {
*out = *in
diff --git a/integrations/operator/config/crd/bases/resources.teleport.dev_appsv3.yaml b/integrations/operator/config/crd/bases/resources.teleport.dev_appsv3.yaml
new file mode 100644
index 0000000000000..9733ab327d7b8
--- /dev/null
+++ b/integrations/operator/config/crd/bases/resources.teleport.dev_appsv3.yaml
@@ -0,0 +1,335 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportappsv3.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportAppV3
+ listKind: TeleportAppV3List
+ plural: teleportappsv3
+ shortNames:
+ - appv3
+ - appsv3
+ singular: teleportappv3
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: AppV3 is the Schema for the appsv3 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
+ spec:
+ description: App resource definition v3 from Teleport
+ properties:
+ UserGroups:
+ description: UserGroups are a list of user group IDs that this app
+ is associated with.
+ items:
+ type: string
+ nullable: true
+ type: array
+ aws:
+ description: AWS contains additional options for AWS applications.
+ nullable: true
+ properties:
+ external_id:
+ description: ExternalID is the AWS External ID used when assuming
+ roles in this app.
+ type: string
+ roles_anywhere_profile:
+ description: RolesAnywhereProfile contains the IAM Roles Anywhere
+ fields associated with this Application. These fields are set
+ when performing the synchronization of AWS IAM Roles Anywhere
+ Profiles into Teleport Apps.
+ nullable: true
+ properties:
+ accept_role_session_name:
+ description: 'Whether this Roles Anywhere Profile accepts
+ a custom role session name. When not supported, the AWS
+ Session Name will be the X.509 certificate''s serial number.
+ When supported, the AWS Session Name will be the identity''s
+ username. This values comes from: https://docs.aws.amazon.com/rolesanywhere/latest/APIReference/API_ProfileDetail.html
+ / acceptRoleSessionName'
+ type: boolean
+ profile_arn:
+ description: ProfileARN is the AWS IAM Roles Anywhere Profile
+ ARN that originated this Teleport App.
+ type: string
+ type: object
+ type: object
+ cloud:
+ description: Cloud identifies the cloud instance the app represents.
+ type: string
+ cors:
+ description: CORSPolicy defines the Cross-Origin Resource Sharing
+ settings for the app.
+ nullable: true
+ properties:
+ allow_credentials:
+ description: allow_credentials indicates whether credentials are
+ allowed.
+ type: boolean
+ allowed_headers:
+ description: allowed_headers specifies which headers can be used
+ when accessing the app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ allowed_methods:
+ description: allowed_methods specifies which methods are allowed
+ when accessing the app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ allowed_origins:
+ description: allowed_origins specifies which origins are allowed
+ to access the app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ exposed_headers:
+ description: exposed_headers indicates which headers are made
+ available to scripts via the browser.
+ items:
+ type: string
+ nullable: true
+ type: array
+ max_age:
+ description: max_age indicates how long (in seconds) the results
+ of a preflight request can be cached.
+ format: int32
+ type: integer
+ type: object
+ dynamic_labels:
+ description: DynamicLabels are the app's command labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ identity_center:
+ description: IdentityCenter encasulates AWS identity-center specific
+ information. Only valid for Identity Center account apps.
+ nullable: true
+ properties:
+ account_id:
+ description: Account ID is the AWS-assigned ID of the account
+ type: string
+ permission_sets:
+ description: PermissionSets lists the available permission sets
+ on the given account
+ items:
+ properties:
+ arn:
+ description: ARN is the fully-formed ARN of the Permission
+ Set.
+ type: string
+ assignment_name:
+ description: AssignmentID is the ID of the Teleport Account
+ Assignment resource that represents this permission being
+ assigned on the enclosing Account.
+ type: string
+ name:
+ description: Name is the human-readable name of the Permission
+ Set.
+ type: string
+ type: object
+ nullable: true
+ type: array
+ type: object
+ insecure_skip_verify:
+ description: InsecureSkipVerify disables app's TLS certificate verification.
+ type: boolean
+ integration:
+ description: Integration is the integration name that must be used
+ to access this Application. Only applicable to AWS App Access. If
+ present, the Application must use the Integration's credentials
+ instead of ambient credentials to access Cloud APIs.
+ type: string
+ public_addr:
+ description: PublicAddr is the public address the application is accessible
+ at.
+ type: string
+ required_app_names:
+ description: RequiredAppNames is a list of app names that are required
+ for this app to function. Any app listed here will be part of the
+ authentication redirect flow and authenticate along side this app.
+ items:
+ type: string
+ nullable: true
+ type: array
+ rewrite:
+ description: Rewrite is a list of rewriting rules to apply to requests
+ and responses.
+ nullable: true
+ properties:
+ headers:
+ description: Headers is a list of headers to inject when passing
+ the request over to the application.
+ items:
+ properties:
+ name:
+ description: Name is the http header name.
+ type: string
+ value:
+ description: Value is the http header value.
+ type: string
+ type: object
+ nullable: true
+ type: array
+ jwt_claims:
+ description: JWTClaims configures whether roles/traits are included
+ in the JWT token.
+ type: string
+ redirect:
+ description: Redirect defines a list of hosts which will be rewritten
+ to the public address of the application if they occur in the
+ "Location" header.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ tcp_ports:
+ description: TCPPorts is a list of ports and port ranges that an app
+ agent can forward connections to. Only applicable to TCP App Access.
+ If this field is not empty, URI is expected to contain no port number
+ and start with the tcp protocol.
+ items:
+ properties:
+ end_port:
+ description: EndPort describes the end of the range, inclusive.
+ If set, it must be between 2 and 65535 and be greater than
+ Port when describing a port range. When omitted or set to
+ zero, it signifies that the port range defines a single port.
+ format: int32
+ type: integer
+ port:
+ description: Port describes the start of the range. It must
+ be between 1 and 65535.
+ format: int32
+ type: integer
+ type: object
+ nullable: true
+ type: array
+ uri:
+ description: URI is the web app endpoint.
+ type: string
+ use_any_proxy_public_addr:
+ description: UseAnyProxyPublicAddr will rebuild this app's fqdn based
+ on the proxy public addr that the request originated from. This
+ should be true if your proxy has multiple proxy public addrs and
+ you want the app to be accessible from any of them. If `public_addr`
+ is explicitly set in the app spec, setting this value to true will
+ overwrite that public address in the web UI.
+ type: boolean
+ 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
diff --git a/integrations/operator/controllers/resources/appv3_controller.go b/integrations/operator/controllers/resources/appv3_controller.go
new file mode 100644
index 0000000000000..71297ea8ae786
--- /dev/null
+++ b/integrations/operator/controllers/resources/appv3_controller.go
@@ -0,0 +1,75 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 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 .
+ */
+
+package resources
+
+import (
+ "context"
+
+ "github.com/gravitational/trace"
+ kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/gravitational/teleport/api/client"
+ "github.com/gravitational/teleport/api/types"
+ 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"
+)
+
+// appClient implements TeleportResourceClient and offers CRUD methods needed to reconcile apps
+// Currently the same client is used by all app versions. If we need to treat
+// them differently at some point, for example by adding a Mutate function
+// functions, we can always split the client into separate clients.
+type appClient struct {
+ teleportClient *client.Client
+}
+
+// Get gets the Teleport app of a given name
+func (r appClient) Get(ctx context.Context, name string) (types.Application, error) {
+ app, err := r.teleportClient.GetApp(ctx, name)
+ return app, trace.Wrap(err)
+}
+
+// Create creates a Teleport app
+func (r appClient) Create(ctx context.Context, app types.Application) error {
+ return trace.Wrap(r.teleportClient.CreateApp(ctx, app))
+}
+
+// Update updates a Teleport app
+func (r appClient) Update(ctx context.Context, app types.Application) error {
+ return trace.Wrap(r.teleportClient.UpdateApp(ctx, app))
+}
+
+// Delete deletes a Teleport app
+func (r appClient) Delete(ctx context.Context, name string) error {
+ return trace.Wrap(r.teleportClient.DeleteApp(ctx, name))
+}
+
+// NewAppV3Reconciler instantiates a new Kubernetes controller reconciling app v6 resources
+func NewAppV3Reconciler(client kclient.Client, tClient *client.Client) (controllers.Reconciler, error) {
+ appClient := &appClient{
+ teleportClient: tClient,
+ }
+
+ resourceReconciler, err := reconcilers.NewTeleportResourceWithLabelsReconciler[types.Application, *resourcesv1.TeleportAppV3](
+ client,
+ appClient,
+ )
+
+ return resourceReconciler, trace.Wrap(err, "building teleport resource reconciler")
+}
diff --git a/integrations/operator/controllers/resources/appv3_controller_test.go b/integrations/operator/controllers/resources/appv3_controller_test.go
new file mode 100644
index 0000000000000..d84c41822c466
--- /dev/null
+++ b/integrations/operator/controllers/resources/appv3_controller_test.go
@@ -0,0 +1,139 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 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 .
+ */
+
+package resources_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/gravitational/trace"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "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/testlib"
+)
+
+var appV3Spec = types.AppSpecV3{
+ URI: "http://test.namespace.svc.cluster.local:8080",
+ Rewrite: &types.Rewrite{
+ Headers: []*types.Header{
+ {
+ Name: "C-Custom-Header",
+ Value: "example",
+ },
+ },
+ },
+}
+
+type appV3TestingPrimitives struct {
+ setup *testSetup
+ reconcilers.ResourceWithLabelsAdapter[types.Application]
+}
+
+func (g *appV3TestingPrimitives) Init(setup *testSetup) {
+ g.setup = setup
+}
+
+func (g *appV3TestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
+ return nil
+}
+
+func (g *appV3TestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
+ meta := types.Metadata{
+ Name: name,
+ }
+ app, err := types.NewAppV3(meta, appV3Spec)
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ app.SetOrigin(types.OriginKubernetes)
+ return trace.Wrap(g.setup.TeleportClient.CreateApp(ctx, app))
+}
+
+func (g *appV3TestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.Application, error) {
+ return g.setup.TeleportClient.GetApp(ctx, name)
+}
+
+func (g *appV3TestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
+ return trace.Wrap(g.setup.TeleportClient.DeleteApp(ctx, name))
+}
+
+func (g *appV3TestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
+ app := &resourcesv1.TeleportAppV3{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: g.setup.Namespace.Name,
+ },
+ Spec: resourcesv1.TeleportAppV3Spec(appV3Spec),
+ }
+ return trace.Wrap(g.setup.K8sClient.Create(ctx, app))
+}
+
+func (g *appV3TestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
+ app := &resourcesv1.TeleportAppV3{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: g.setup.Namespace.Name,
+ },
+ }
+ return trace.Wrap(g.setup.K8sClient.Delete(ctx, app))
+}
+
+func (g *appV3TestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportAppV3, error) {
+ app := &resourcesv1.TeleportAppV3{}
+ obj := kclient.ObjectKey{
+ Name: name,
+ Namespace: g.setup.Namespace.Name,
+ }
+ err := g.setup.K8sClient.Get(ctx, obj, app)
+ return app, trace.Wrap(err)
+}
+
+func (g *appV3TestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
+ app, err := g.GetKubernetesResource(ctx, name)
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ app.Spec.Rewrite.Headers = append(app.Spec.Rewrite.Headers, &types.Header{Name: "Content-Type", Value: "application/json"})
+ return g.setup.K8sClient.Update(ctx, app)
+}
+
+func (g *appV3TestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.Application, kubeResource *resourcesv1.TeleportAppV3) (bool, string) {
+ diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
+ return diff == "", diff
+}
+
+func TestTeleportAppV3Creation(t *testing.T) {
+ test := &appV3TestingPrimitives{}
+ testlib.ResourceCreationTest[types.Application, *resourcesv1.TeleportAppV3](t, test)
+}
+
+func TestTeleportAppV3DeletionDrift(t *testing.T) {
+ test := &appV3TestingPrimitives{}
+ testlib.ResourceDeletionDriftTest[types.Application, *resourcesv1.TeleportAppV3](t, test)
+}
+
+func TestTeleportAppV3Update(t *testing.T) {
+ test := &appV3TestingPrimitives{}
+ testlib.ResourceUpdateTest[types.Application, *resourcesv1.TeleportAppV3](t, test)
+}
diff --git a/integrations/operator/controllers/resources/setup.go b/integrations/operator/controllers/resources/setup.go
index 43eb6cc1b250c..a8442d84e0805 100644
--- a/integrations/operator/controllers/resources/setup.go
+++ b/integrations/operator/controllers/resources/setup.go
@@ -52,6 +52,7 @@ func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *c
{"TeleportWorkloadIdentityV1", NewWorkloadIdentityV1Reconciler},
{"TeleportAutoupdateConfigV1", NewAutoUpdateConfigV1Reconciler},
{"TeleportAutoupdateVersionV1", NewAutoUpdateVersionV1Reconciler},
+ {"TeleportAppV3", NewAppV3Reconciler},
}
oidc := modules.GetProtoEntitlement(features, entitlements.OIDC)
diff --git a/integrations/operator/controllers/resources/testlib/env.go b/integrations/operator/controllers/resources/testlib/env.go
index 1e8eaf4597be3..4084a8011676c 100644
--- a/integrations/operator/controllers/resources/testlib/env.go
+++ b/integrations/operator/controllers/resources/testlib/env.go
@@ -129,6 +129,7 @@ func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string)
// the operator has wildcard noe labs to be able to see them
// but has no login allowed, so it cannot SSH into them
NodeLabels: types.Labels{"*": []string{"*"}},
+ AppLabels: types.Labels{"*": []string{"*"}},
Rules: []types.Rule{
types.NewRule(types.KindRole, unrestricted),
types.NewRule(types.KindUser, unrestricted),
@@ -143,6 +144,7 @@ func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string)
types.NewRule(types.KindWorkloadIdentity, unrestricted),
types.NewRule(types.KindAutoUpdateConfig, unrestricted),
types.NewRule(types.KindAutoUpdateVersion, unrestricted),
+ types.NewRule(types.KindApp, unrestricted),
},
},
})
diff --git a/integrations/operator/crdgen/handlerequest.go b/integrations/operator/crdgen/handlerequest.go
index 040f30e08f29a..f6b49cf8ecbf6 100644
--- a/integrations/operator/crdgen/handlerequest.go
+++ b/integrations/operator/crdgen/handlerequest.go
@@ -177,6 +177,7 @@ func generateSchema(file *File, groupName string, format crdFormatFunc, resp *go
// Role V7 and V8 is using the RoleV6 message
{name: "RoleV6", opts: []resourceSchemaOption{withVersionOverride(types.V7), withVersionInKindOverride()}},
{name: "RoleV6", opts: []resourceSchemaOption{withVersionOverride(types.V8), withVersionInKindOverride()}},
+ {name: "AppV3", opts: []resourceSchemaOption{withVersionOverride(types.V3), withVersionInKindOverride()}},
{name: "SAMLConnectorV2"},
{name: "OIDCConnectorV3"},
{name: "GithubConnectorV3"},
diff --git a/integrations/operator/hack/fixture-operator-role.yaml b/integrations/operator/hack/fixture-operator-role.yaml
index f0c91a5db57f7..97ed5be4c2c50 100644
--- a/integrations/operator/hack/fixture-operator-role.yaml
+++ b/integrations/operator/hack/fixture-operator-role.yaml
@@ -8,6 +8,8 @@ spec:
# However, it has no login set, so it cannot SSH on them
node_labels:
"*": ["*"]
+ app_labels:
+ "*": ["*"]
rules:
- resources:
- role
@@ -97,6 +99,14 @@ spec:
- read
- update
- delete
+ - resources:
+ - app
+ verbs:
+ - list
+ - create
+ - read
+ - update
+ - delete
- resources:
- autoupdate_version
verbs: