github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/app.go (about)

     1  /*
     2  Copyright 2021 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"fmt"
    21  	"net/url"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/gravitational/trace"
    26  
    27  	"github.com/gravitational/teleport/api/constants"
    28  	"github.com/gravitational/teleport/api/types/compare"
    29  	"github.com/gravitational/teleport/api/utils"
    30  )
    31  
    32  var _ compare.IsEqual[Application] = (*AppV3)(nil)
    33  
    34  // Application represents a web, TCP or cloud console application.
    35  type Application interface {
    36  	// ResourceWithLabels provides common resource methods.
    37  	ResourceWithLabels
    38  	// GetNamespace returns the app namespace.
    39  	GetNamespace() string
    40  	// GetStaticLabels returns the app static labels.
    41  	GetStaticLabels() map[string]string
    42  	// SetStaticLabels sets the app static labels.
    43  	SetStaticLabels(map[string]string)
    44  	// GetDynamicLabels returns the app dynamic labels.
    45  	GetDynamicLabels() map[string]CommandLabel
    46  	// SetDynamicLabels sets the app dynamic labels.
    47  	SetDynamicLabels(map[string]CommandLabel)
    48  	// String returns string representation of the app.
    49  	String() string
    50  	// GetDescription returns the app description.
    51  	GetDescription() string
    52  	// GetURI returns the app connection endpoint.
    53  	GetURI() string
    54  	// SetURI sets the app endpoint.
    55  	SetURI(string)
    56  	// GetPublicAddr returns the app public address.
    57  	GetPublicAddr() string
    58  	// GetInsecureSkipVerify returns the app insecure setting.
    59  	GetInsecureSkipVerify() bool
    60  	// GetRewrite returns the app rewrite configuration.
    61  	GetRewrite() *Rewrite
    62  	// IsAWSConsole returns true if this app is AWS management console.
    63  	IsAWSConsole() bool
    64  	// IsAzureCloud returns true if this app represents Azure Cloud instance.
    65  	IsAzureCloud() bool
    66  	// IsGCP returns true if this app represents GCP instance.
    67  	IsGCP() bool
    68  	// IsTCP returns true if this app represents a TCP endpoint.
    69  	IsTCP() bool
    70  	// GetProtocol returns the application protocol.
    71  	GetProtocol() string
    72  	// GetAWSAccountID returns value of label containing AWS account ID on this app.
    73  	GetAWSAccountID() string
    74  	// GetAWSExternalID returns the AWS External ID configured for this app.
    75  	GetAWSExternalID() string
    76  	// GetUserGroups will get the list of user group IDs associated with the application.
    77  	GetUserGroups() []string
    78  	// SetUserGroups will set the list of user group IDs associated with the application.
    79  	SetUserGroups([]string)
    80  	// Copy returns a copy of this app resource.
    81  	Copy() *AppV3
    82  	// GetIntegration will return the Integration.
    83  	// If present, the Application must use the Integration's credentials instead of ambient credentials to access Cloud APIs.
    84  	GetIntegration() string
    85  }
    86  
    87  // NewAppV3 creates a new app resource.
    88  func NewAppV3(meta Metadata, spec AppSpecV3) (*AppV3, error) {
    89  	app := &AppV3{
    90  		Metadata: meta,
    91  		Spec:     spec,
    92  	}
    93  	if err := app.CheckAndSetDefaults(); err != nil {
    94  		return nil, trace.Wrap(err)
    95  	}
    96  	return app, nil
    97  }
    98  
    99  // GetVersion returns the app resource version.
   100  func (a *AppV3) GetVersion() string {
   101  	return a.Version
   102  }
   103  
   104  // GetKind returns the app resource kind.
   105  func (a *AppV3) GetKind() string {
   106  	return a.Kind
   107  }
   108  
   109  // GetSubKind returns the app resource subkind.
   110  func (a *AppV3) GetSubKind() string {
   111  	return a.SubKind
   112  }
   113  
   114  // SetSubKind sets the app resource subkind.
   115  func (a *AppV3) SetSubKind(sk string) {
   116  	a.SubKind = sk
   117  }
   118  
   119  // GetResourceID returns the app resource ID.
   120  func (a *AppV3) GetResourceID() int64 {
   121  	return a.Metadata.ID
   122  }
   123  
   124  // SetResourceID sets the app resource ID.
   125  func (a *AppV3) SetResourceID(id int64) {
   126  	a.Metadata.ID = id
   127  }
   128  
   129  // GetRevision returns the revision
   130  func (a *AppV3) GetRevision() string {
   131  	return a.Metadata.GetRevision()
   132  }
   133  
   134  // SetRevision sets the revision
   135  func (a *AppV3) SetRevision(rev string) {
   136  	a.Metadata.SetRevision(rev)
   137  }
   138  
   139  // GetMetadata returns the app resource metadata.
   140  func (a *AppV3) GetMetadata() Metadata {
   141  	return a.Metadata
   142  }
   143  
   144  // Origin returns the origin value of the resource.
   145  func (a *AppV3) Origin() string {
   146  	return a.Metadata.Origin()
   147  }
   148  
   149  // SetOrigin sets the origin value of the resource.
   150  func (a *AppV3) SetOrigin(origin string) {
   151  	a.Metadata.SetOrigin(origin)
   152  }
   153  
   154  // GetNamespace returns the app resource namespace.
   155  func (a *AppV3) GetNamespace() string {
   156  	return a.Metadata.Namespace
   157  }
   158  
   159  // SetExpiry sets the app resource expiration time.
   160  func (a *AppV3) SetExpiry(expiry time.Time) {
   161  	a.Metadata.SetExpiry(expiry)
   162  }
   163  
   164  // Expiry returns the app resource expiration time.
   165  func (a *AppV3) Expiry() time.Time {
   166  	return a.Metadata.Expiry()
   167  }
   168  
   169  // GetName returns the app resource name.
   170  func (a *AppV3) GetName() string {
   171  	return a.Metadata.Name
   172  }
   173  
   174  // SetName sets the app resource name.
   175  func (a *AppV3) SetName(name string) {
   176  	a.Metadata.Name = name
   177  }
   178  
   179  // GetStaticLabels returns the app static labels.
   180  func (a *AppV3) GetStaticLabels() map[string]string {
   181  	return a.Metadata.Labels
   182  }
   183  
   184  // SetStaticLabels sets the app static labels.
   185  func (a *AppV3) SetStaticLabels(sl map[string]string) {
   186  	a.Metadata.Labels = sl
   187  }
   188  
   189  // GetDynamicLabels returns the app dynamic labels.
   190  func (a *AppV3) GetDynamicLabels() map[string]CommandLabel {
   191  	if a.Spec.DynamicLabels == nil {
   192  		return nil
   193  	}
   194  	return V2ToLabels(a.Spec.DynamicLabels)
   195  }
   196  
   197  // SetDynamicLabels sets the app dynamic labels
   198  func (a *AppV3) SetDynamicLabels(dl map[string]CommandLabel) {
   199  	a.Spec.DynamicLabels = LabelsToV2(dl)
   200  }
   201  
   202  // GetLabel retrieves the label with the provided key. If not found
   203  // value will be empty and ok will be false.
   204  func (a *AppV3) GetLabel(key string) (value string, ok bool) {
   205  	if cmd, ok := a.Spec.DynamicLabels[key]; ok {
   206  		return cmd.Result, ok
   207  	}
   208  
   209  	v, ok := a.Metadata.Labels[key]
   210  	return v, ok
   211  }
   212  
   213  // GetAllLabels returns the app combined static and dynamic labels.
   214  func (a *AppV3) GetAllLabels() map[string]string {
   215  	return CombineLabels(a.Metadata.Labels, a.Spec.DynamicLabels)
   216  }
   217  
   218  // GetDescription returns the app description.
   219  func (a *AppV3) GetDescription() string {
   220  	return a.Metadata.Description
   221  }
   222  
   223  // GetURI returns the app connection address.
   224  func (a *AppV3) GetURI() string {
   225  	return a.Spec.URI
   226  }
   227  
   228  // SetURI sets the app connection address.
   229  func (a *AppV3) SetURI(uri string) {
   230  	a.Spec.URI = uri
   231  }
   232  
   233  // GetPublicAddr returns the app public address.
   234  func (a *AppV3) GetPublicAddr() string {
   235  	return a.Spec.PublicAddr
   236  }
   237  
   238  // GetInsecureSkipVerify returns the app insecure setting.
   239  func (a *AppV3) GetInsecureSkipVerify() bool {
   240  	return a.Spec.InsecureSkipVerify
   241  }
   242  
   243  // GetRewrite returns the app rewrite configuration.
   244  func (a *AppV3) GetRewrite() *Rewrite {
   245  	return a.Spec.Rewrite
   246  }
   247  
   248  // IsAWSConsole returns true if this app is AWS management console.
   249  func (a *AppV3) IsAWSConsole() bool {
   250  	// TODO(greedy52) support region based console URL like:
   251  	// https://us-east-1.console.aws.amazon.com/
   252  	for _, consoleURL := range []string{
   253  		constants.AWSConsoleURL,
   254  		constants.AWSUSGovConsoleURL,
   255  		constants.AWSCNConsoleURL,
   256  	} {
   257  		if strings.HasPrefix(a.Spec.URI, consoleURL) {
   258  			return true
   259  		}
   260  	}
   261  
   262  	return a.Spec.Cloud == CloudAWS
   263  }
   264  
   265  // IsAzureCloud returns true if this app is Azure Cloud instance.
   266  func (a *AppV3) IsAzureCloud() bool {
   267  	return a.Spec.Cloud == CloudAzure
   268  }
   269  
   270  // IsGCP returns true if this app is GCP instance.
   271  func (a *AppV3) IsGCP() bool {
   272  	return a.Spec.Cloud == CloudGCP
   273  }
   274  
   275  // IsTCP returns true if this app represents a TCP endpoint.
   276  func (a *AppV3) IsTCP() bool {
   277  	return IsAppTCP(a.Spec.URI)
   278  }
   279  
   280  func IsAppTCP(uri string) bool {
   281  	return strings.HasPrefix(uri, "tcp://")
   282  }
   283  
   284  // GetProtocol returns the application protocol.
   285  func (a *AppV3) GetProtocol() string {
   286  	if a.IsTCP() {
   287  		return "TCP"
   288  	}
   289  	return "HTTP"
   290  }
   291  
   292  // GetAWSAccountID returns value of label containing AWS account ID on this app.
   293  func (a *AppV3) GetAWSAccountID() string {
   294  	return a.Metadata.Labels[constants.AWSAccountIDLabel]
   295  }
   296  
   297  // GetAWSExternalID returns the AWS External ID configured for this app.
   298  func (a *AppV3) GetAWSExternalID() string {
   299  	if a.Spec.AWS == nil {
   300  		return ""
   301  	}
   302  	return a.Spec.AWS.ExternalID
   303  }
   304  
   305  // GetUserGroups will get the list of user group IDss associated with the application.
   306  func (a *AppV3) GetUserGroups() []string {
   307  	return a.Spec.UserGroups
   308  }
   309  
   310  // SetUserGroups will set the list of user group IDs associated with the application.
   311  func (a *AppV3) SetUserGroups(userGroups []string) {
   312  	a.Spec.UserGroups = userGroups
   313  }
   314  
   315  // GetIntegration will return the Integration.
   316  // If present, the Application must use the Integration's credentials instead of ambient credentials to access Cloud APIs.
   317  func (a *AppV3) GetIntegration() string {
   318  	return a.Spec.Integration
   319  }
   320  
   321  // String returns the app string representation.
   322  func (a *AppV3) String() string {
   323  	return fmt.Sprintf("App(Name=%v, PublicAddr=%v, Labels=%v)",
   324  		a.GetName(), a.GetPublicAddr(), a.GetAllLabels())
   325  }
   326  
   327  // Copy returns a copy of this database resource.
   328  func (a *AppV3) Copy() *AppV3 {
   329  	return utils.CloneProtoMsg(a)
   330  }
   331  
   332  // MatchSearch goes through select field values and tries to
   333  // match against the list of search values.
   334  func (a *AppV3) MatchSearch(values []string) bool {
   335  	fieldVals := append(utils.MapToStrings(a.GetAllLabels()), a.GetName(), a.GetDescription(), a.GetPublicAddr())
   336  	return MatchSearch(fieldVals, values, nil)
   337  }
   338  
   339  // setStaticFields sets static resource header and metadata fields.
   340  func (a *AppV3) setStaticFields() {
   341  	a.Kind = KindApp
   342  	a.Version = V3
   343  }
   344  
   345  // CheckAndSetDefaults checks and sets default values for any missing fields.
   346  func (a *AppV3) CheckAndSetDefaults() error {
   347  	a.setStaticFields()
   348  	if err := a.Metadata.CheckAndSetDefaults(); err != nil {
   349  		return trace.Wrap(err)
   350  	}
   351  	for key := range a.Spec.DynamicLabels {
   352  		if !IsValidLabelKey(key) {
   353  			return trace.BadParameter("app %q invalid label key: %q", a.GetName(), key)
   354  		}
   355  	}
   356  	if a.Spec.URI == "" {
   357  		if a.Spec.Cloud != "" {
   358  			a.Spec.URI = fmt.Sprintf("cloud://%v", a.Spec.Cloud)
   359  		} else {
   360  			return trace.BadParameter("app %q URI is empty", a.GetName())
   361  		}
   362  	}
   363  	if a.Spec.Cloud == "" && a.IsAWSConsole() {
   364  		a.Spec.Cloud = CloudAWS
   365  	}
   366  	switch a.Spec.Cloud {
   367  	case "", CloudAWS, CloudAzure, CloudGCP:
   368  		break
   369  	default:
   370  		return trace.BadParameter("app %q has unexpected Cloud value %q", a.GetName(), a.Spec.Cloud)
   371  	}
   372  	url, err := url.Parse(a.Spec.PublicAddr)
   373  	if err != nil {
   374  		return trace.BadParameter("invalid PublicAddr format: %v", err)
   375  	}
   376  	host := a.Spec.PublicAddr
   377  	if url.Host != "" {
   378  		host = url.Host
   379  	}
   380  
   381  	if strings.HasPrefix(host, constants.KubeTeleportProxyALPNPrefix) {
   382  		return trace.BadParameter("app %q DNS prefix found in %q public_url is reserved for internal usage",
   383  			constants.KubeTeleportProxyALPNPrefix, a.Spec.PublicAddr)
   384  	}
   385  
   386  	if a.Spec.Rewrite != nil {
   387  		switch a.Spec.Rewrite.JWTClaims {
   388  		case "", JWTClaimsRewriteRolesAndTraits, JWTClaimsRewriteRoles, JWTClaimsRewriteNone, JWTClaimsRewriteTraits:
   389  		default:
   390  			return trace.BadParameter("app %q has unexpected JWT rewrite value %q", a.GetName(), a.Spec.Rewrite.JWTClaims)
   391  
   392  		}
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  // IsEqual determines if two application resources are equivalent to one another.
   399  func (a *AppV3) IsEqual(i Application) bool {
   400  	if other, ok := i.(*AppV3); ok {
   401  		return deriveTeleportEqualAppV3(a, other)
   402  	}
   403  	return false
   404  }
   405  
   406  // DeduplicateApps deduplicates apps by combination of app name and public address.
   407  // Apps can have the same name but also could have different addresses.
   408  func DeduplicateApps(apps []Application) (result []Application) {
   409  	type key struct{ name, addr string }
   410  	seen := make(map[key]struct{})
   411  	for _, app := range apps {
   412  		key := key{app.GetName(), app.GetPublicAddr()}
   413  		if _, ok := seen[key]; ok {
   414  			continue
   415  		}
   416  		seen[key] = struct{}{}
   417  		result = append(result, app)
   418  	}
   419  	return result
   420  }
   421  
   422  // Apps is a list of app resources.
   423  type Apps []Application
   424  
   425  // Find returns app with the specified name or nil.
   426  func (a Apps) Find(name string) Application {
   427  	for _, app := range a {
   428  		if app.GetName() == name {
   429  			return app
   430  		}
   431  	}
   432  	return nil
   433  }
   434  
   435  // AsResources returns these apps as resources with labels.
   436  func (a Apps) AsResources() (resources ResourcesWithLabels) {
   437  	for _, app := range a {
   438  		resources = append(resources, app)
   439  	}
   440  	return resources
   441  }
   442  
   443  // Len returns the slice length.
   444  func (a Apps) Len() int { return len(a) }
   445  
   446  // Less compares apps by name.
   447  func (a Apps) Less(i, j int) bool { return a[i].GetName() < a[j].GetName() }
   448  
   449  // Swap swaps two apps.
   450  func (a Apps) Swap(i, j int) { a[i], a[j] = a[j], a[i] }