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] }