github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/provisioning.go (about) 1 /* 2 Copyright 2020-2022 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 "crypto/x509" 21 "encoding/pem" 22 "fmt" 23 "slices" 24 "strings" 25 "time" 26 27 "github.com/gravitational/trace" 28 29 "github.com/gravitational/teleport/api/defaults" 30 apiutils "github.com/gravitational/teleport/api/utils" 31 ) 32 33 // JoinMethod is the method used for new nodes to join the cluster. 34 type JoinMethod string 35 36 const ( 37 JoinMethodUnspecified JoinMethod = "" 38 // JoinMethodToken is the default join method, nodes join the cluster by 39 // presenting a secret token. 40 JoinMethodToken JoinMethod = "token" 41 // JoinMethodEC2 indicates that the node will join with the EC2 join method. 42 JoinMethodEC2 JoinMethod = "ec2" 43 // JoinMethodIAM indicates that the node will join with the IAM join method. 44 JoinMethodIAM JoinMethod = "iam" 45 // JoinMethodGitHub indicates that the node will join with the GitHub join 46 // method. Documentation regarding the implementation of this can be found 47 // in lib/githubactions 48 JoinMethodGitHub JoinMethod = "github" 49 // JoinMethodCircleCI indicates that the node will join with the CircleCI\ 50 // join method. Documentation regarding the implementation of this can be 51 // found in lib/circleci 52 JoinMethodCircleCI JoinMethod = "circleci" 53 // JoinMethodKubernetes indicates that the node will join with the 54 // Kubernetes join method. Documentation regarding implementation can be 55 // found in lib/kubernetestoken 56 JoinMethodKubernetes JoinMethod = "kubernetes" 57 // JoinMethodAzure indicates that the node will join with the Azure join 58 // method. 59 JoinMethodAzure JoinMethod = "azure" 60 // JoinMethodGitLab indicates that the node will join with the GitLab 61 // join method. Documentation regarding implementation of this 62 // can be found in lib/gitlab 63 JoinMethodGitLab JoinMethod = "gitlab" 64 // JoinMethodGCP indicates that the node will join with the GCP join method. 65 // Documentation regarding implementation of this can be found in lib/gcp. 66 JoinMethodGCP JoinMethod = "gcp" 67 // JoinMethodSpacelift indicates the node will join with the SpaceLift join 68 // method. Documentation regarding implementation of this can be found in 69 // lib/spacelift. 70 JoinMethodSpacelift JoinMethod = "spacelift" 71 // JoinMethodTPM indicates that the node will join with the TPM join method. 72 // The core implementation of this join method can be found in lib/tpm. 73 JoinMethodTPM JoinMethod = "tpm" 74 ) 75 76 var JoinMethods = []JoinMethod{ 77 JoinMethodAzure, 78 JoinMethodCircleCI, 79 JoinMethodEC2, 80 JoinMethodGCP, 81 JoinMethodGitHub, 82 JoinMethodGitLab, 83 JoinMethodIAM, 84 JoinMethodKubernetes, 85 JoinMethodSpacelift, 86 JoinMethodToken, 87 JoinMethodTPM, 88 } 89 90 func ValidateJoinMethod(method JoinMethod) error { 91 hasJoinMethod := slices.Contains(JoinMethods, method) 92 if !hasJoinMethod { 93 return trace.BadParameter("join method must be one of %s", apiutils.JoinStrings(JoinMethods, ", ")) 94 } 95 96 return nil 97 } 98 99 type KubernetesJoinType string 100 101 var ( 102 KubernetesJoinTypeUnspecified KubernetesJoinType = "" 103 KubernetesJoinTypeInCluster KubernetesJoinType = "in_cluster" 104 KubernetesJoinTypeStaticJWKS KubernetesJoinType = "static_jwks" 105 ) 106 107 // ProvisionToken is a provisioning token 108 type ProvisionToken interface { 109 ResourceWithOrigin 110 // SetMetadata sets resource metatada 111 SetMetadata(meta Metadata) 112 // GetRoles returns a list of teleport roles 113 // that will be granted to the user of the token 114 // in the crendentials 115 GetRoles() SystemRoles 116 // SetRoles sets teleport roles 117 SetRoles(SystemRoles) 118 // SetLabels sets the tokens labels 119 SetLabels(map[string]string) 120 // GetAllowRules returns the list of allow rules 121 GetAllowRules() []*TokenRule 122 // SetAllowRules sets the allow rules 123 SetAllowRules([]*TokenRule) 124 // GetAWSIIDTTL returns the TTL of EC2 IIDs 125 GetAWSIIDTTL() Duration 126 // GetJoinMethod returns joining method that must be used with this token. 127 GetJoinMethod() JoinMethod 128 // GetBotName returns the BotName field which must be set for joining bots. 129 GetBotName() string 130 131 // GetSuggestedLabels returns the set of labels that the resource should add when adding itself to the cluster 132 GetSuggestedLabels() Labels 133 134 // GetSuggestedAgentMatcherLabels returns the set of labels that should be watched when an agent/service uses this token. 135 // An example of this is the Database Agent. 136 // When using the install-database.sh script, the script will add those labels as part of the `teleport.yaml` configuration. 137 // They are added to `db_service.resources.0.labels`. 138 GetSuggestedAgentMatcherLabels() Labels 139 140 // V1 returns V1 version of the resource 141 V1() *ProvisionTokenV1 142 // String returns user friendly representation of the resource 143 String() string 144 145 // GetSafeName returns the name of the token, sanitized appropriately for 146 // join methods where the name is secret. This should be used when logging 147 // the token name. 148 GetSafeName() string 149 } 150 151 // NewProvisionToken returns a new provision token with the given roles. 152 func NewProvisionToken(token string, roles SystemRoles, expires time.Time) (ProvisionToken, error) { 153 return NewProvisionTokenFromSpec(token, expires, ProvisionTokenSpecV2{ 154 Roles: roles, 155 }) 156 } 157 158 // NewProvisionTokenFromSpec returns a new provision token with the given spec. 159 func NewProvisionTokenFromSpec(token string, expires time.Time, spec ProvisionTokenSpecV2) (ProvisionToken, error) { 160 t := &ProvisionTokenV2{ 161 Metadata: Metadata{ 162 Name: token, 163 Expires: &expires, 164 }, 165 Spec: spec, 166 } 167 if err := t.CheckAndSetDefaults(); err != nil { 168 return nil, trace.Wrap(err) 169 } 170 return t, nil 171 } 172 173 // MustCreateProvisionToken returns a new valid provision token 174 // or panics, used in tests 175 func MustCreateProvisionToken(token string, roles SystemRoles, expires time.Time) ProvisionToken { 176 t, err := NewProvisionToken(token, roles, expires) 177 if err != nil { 178 panic(err) 179 } 180 return t 181 } 182 183 // setStaticFields sets static resource header and metadata fields. 184 func (p *ProvisionTokenV2) setStaticFields() { 185 p.Kind = KindToken 186 p.Version = V2 187 } 188 189 // CheckAndSetDefaults checks and set default values for any missing fields. 190 func (p *ProvisionTokenV2) CheckAndSetDefaults() error { 191 p.setStaticFields() 192 if err := p.Metadata.CheckAndSetDefaults(); err != nil { 193 return trace.Wrap(err) 194 } 195 196 if len(p.Spec.Roles) == 0 { 197 return trace.BadParameter("provisioning token is missing roles") 198 } 199 roles, err := NewTeleportRoles(SystemRoles(p.Spec.Roles).StringSlice()) 200 if err != nil { 201 return trace.Wrap(err) 202 } 203 p.Spec.Roles = roles 204 205 if roles.Include(RoleBot) && p.Spec.BotName == "" { 206 return trace.BadParameter("token with role %q must set bot_name", RoleBot) 207 } 208 209 if p.Spec.BotName != "" && !roles.Include(RoleBot) { 210 return trace.BadParameter("can only set bot_name on token with role %q", RoleBot) 211 } 212 213 hasAllowRules := len(p.Spec.Allow) > 0 214 if p.Spec.JoinMethod == JoinMethodUnspecified { 215 // Default to the ec2 join method if any allow rules were specified, 216 // else default to the token method. These defaults are necessary for 217 // backwards compatibility. 218 if hasAllowRules { 219 p.Spec.JoinMethod = JoinMethodEC2 220 } else { 221 p.Spec.JoinMethod = JoinMethodToken 222 } 223 } 224 switch p.Spec.JoinMethod { 225 case JoinMethodToken: 226 if hasAllowRules { 227 return trace.BadParameter("allow rules are not compatible with the %q join method", JoinMethodToken) 228 } 229 case JoinMethodEC2: 230 if !hasAllowRules { 231 return trace.BadParameter("the %q join method requires defined token allow rules", JoinMethodEC2) 232 } 233 for _, allowRule := range p.Spec.Allow { 234 if allowRule.AWSARN != "" { 235 return trace.BadParameter(`the %q join method does not support the "aws_arn" parameter`, JoinMethodEC2) 236 } 237 if allowRule.AWSAccount == "" && allowRule.AWSRole == "" { 238 return trace.BadParameter(`allow rule for %q join method must set "aws_account" or "aws_role"`, JoinMethodEC2) 239 } 240 } 241 if p.Spec.AWSIIDTTL == 0 { 242 // default to 5 minute ttl if unspecified 243 p.Spec.AWSIIDTTL = Duration(5 * time.Minute) 244 } 245 case JoinMethodIAM: 246 if !hasAllowRules { 247 return trace.BadParameter("the %q join method requires defined token allow rules", JoinMethodIAM) 248 } 249 for _, allowRule := range p.Spec.Allow { 250 if allowRule.AWSRole != "" { 251 return trace.BadParameter(`the %q join method does not support the "aws_role" parameter`, JoinMethodIAM) 252 } 253 if len(allowRule.AWSRegions) != 0 { 254 return trace.BadParameter(`the %q join method does not support the "aws_regions" parameter`, JoinMethodIAM) 255 } 256 if allowRule.AWSAccount == "" && allowRule.AWSARN == "" { 257 return trace.BadParameter(`allow rule for %q join method must set "aws_account" or "aws_arn"`, JoinMethodEC2) 258 } 259 } 260 case JoinMethodGitHub: 261 providerCfg := p.Spec.GitHub 262 if providerCfg == nil { 263 return trace.BadParameter( 264 `"github" configuration must be provided for join method %q`, 265 JoinMethodGitHub, 266 ) 267 } 268 if err := providerCfg.checkAndSetDefaults(); err != nil { 269 return trace.Wrap(err) 270 } 271 case JoinMethodCircleCI: 272 providerCfg := p.Spec.CircleCI 273 if providerCfg == nil { 274 return trace.BadParameter( 275 `"cirleci" configuration must be provided for join method %q`, 276 JoinMethodCircleCI, 277 ) 278 } 279 if err := providerCfg.checkAndSetDefaults(); err != nil { 280 return trace.Wrap(err) 281 } 282 case JoinMethodKubernetes: 283 providerCfg := p.Spec.Kubernetes 284 if providerCfg == nil { 285 return trace.BadParameter( 286 `"kubernetes" configuration must be provided for the join method %q`, 287 JoinMethodKubernetes, 288 ) 289 } 290 if err := providerCfg.checkAndSetDefaults(); err != nil { 291 return trace.Wrap(err, "spec.kubernetes:") 292 } 293 case JoinMethodAzure: 294 providerCfg := p.Spec.Azure 295 if providerCfg == nil { 296 return trace.BadParameter( 297 `"azure" configuration must be provided for the join method %q`, 298 JoinMethodAzure, 299 ) 300 } 301 if err := providerCfg.checkAndSetDefaults(); err != nil { 302 return trace.Wrap(err) 303 } 304 case JoinMethodGitLab: 305 providerCfg := p.Spec.GitLab 306 if providerCfg == nil { 307 return trace.BadParameter( 308 `"gitlab" configuration must be provided for the join method %q`, 309 JoinMethodGitLab, 310 ) 311 } 312 if err := providerCfg.checkAndSetDefaults(); err != nil { 313 return trace.Wrap(err) 314 } 315 case JoinMethodGCP: 316 providerCfg := p.Spec.GCP 317 if providerCfg == nil { 318 return trace.BadParameter( 319 `"gcp" configuration must be provided for the join method %q`, 320 JoinMethodGCP, 321 ) 322 } 323 if err := providerCfg.checkAndSetDefaults(); err != nil { 324 return trace.Wrap(err) 325 } 326 case JoinMethodSpacelift: 327 providerCfg := p.Spec.Spacelift 328 if providerCfg == nil { 329 return trace.BadParameter( 330 `spec.spacelift: must be configured for the join method %q`, 331 JoinMethodSpacelift, 332 ) 333 } 334 if err := providerCfg.checkAndSetDefaults(); err != nil { 335 return trace.Wrap(err, "spec.spacelift: failed validation") 336 } 337 case JoinMethodTPM: 338 providerCfg := p.Spec.TPM 339 if providerCfg == nil { 340 return trace.BadParameter( 341 `spec.tpm: must be configured for the join method %q`, 342 JoinMethodTPM, 343 ) 344 } 345 if err := providerCfg.validate(); err != nil { 346 return trace.Wrap(err, "spec.tpm: failed validation") 347 } 348 default: 349 return trace.BadParameter("unknown join method %q", p.Spec.JoinMethod) 350 } 351 352 return nil 353 } 354 355 // GetVersion returns resource version 356 func (p *ProvisionTokenV2) GetVersion() string { 357 return p.Version 358 } 359 360 // GetRoles returns a list of teleport roles 361 // that will be granted to the user of the token 362 // in the crendentials 363 func (p *ProvisionTokenV2) GetRoles() SystemRoles { 364 // Ensure that roles are case-insensitive. 365 return normalizedSystemRoles(SystemRoles(p.Spec.Roles).StringSlice()) 366 } 367 368 // SetRoles sets teleport roles 369 func (p *ProvisionTokenV2) SetRoles(r SystemRoles) { 370 p.Spec.Roles = r 371 } 372 373 func (p *ProvisionTokenV2) SetLabels(l map[string]string) { 374 p.Metadata.Labels = l 375 } 376 377 // GetAllowRules returns the list of allow rules 378 func (p *ProvisionTokenV2) GetAllowRules() []*TokenRule { 379 return p.Spec.Allow 380 } 381 382 // SetAllowRules sets the allow rules. 383 func (p *ProvisionTokenV2) SetAllowRules(rules []*TokenRule) { 384 p.Spec.Allow = rules 385 } 386 387 // GetAWSIIDTTL returns the TTL of EC2 IIDs 388 func (p *ProvisionTokenV2) GetAWSIIDTTL() Duration { 389 return p.Spec.AWSIIDTTL 390 } 391 392 // GetJoinMethod returns joining method that must be used with this token. 393 func (p *ProvisionTokenV2) GetJoinMethod() JoinMethod { 394 return p.Spec.JoinMethod 395 } 396 397 // GetBotName returns the BotName field which must be set for joining bots. 398 func (p *ProvisionTokenV2) GetBotName() string { 399 return p.Spec.BotName 400 } 401 402 // GetKind returns resource kind 403 func (p *ProvisionTokenV2) GetKind() string { 404 return p.Kind 405 } 406 407 // GetSubKind returns resource sub kind 408 func (p *ProvisionTokenV2) GetSubKind() string { 409 return p.SubKind 410 } 411 412 // SetSubKind sets resource subkind 413 func (p *ProvisionTokenV2) SetSubKind(s string) { 414 p.SubKind = s 415 } 416 417 // GetResourceID returns resource ID 418 func (p *ProvisionTokenV2) GetResourceID() int64 { 419 return p.Metadata.ID 420 } 421 422 // SetResourceID sets resource ID 423 func (p *ProvisionTokenV2) SetResourceID(id int64) { 424 p.Metadata.ID = id 425 } 426 427 // GetRevision returns the revision 428 func (p *ProvisionTokenV2) GetRevision() string { 429 return p.Metadata.GetRevision() 430 } 431 432 // SetRevision sets the revision 433 func (p *ProvisionTokenV2) SetRevision(rev string) { 434 p.Metadata.SetRevision(rev) 435 } 436 437 // GetMetadata returns metadata 438 func (p *ProvisionTokenV2) GetMetadata() Metadata { 439 return p.Metadata 440 } 441 442 // SetMetadata sets resource metatada 443 func (p *ProvisionTokenV2) SetMetadata(meta Metadata) { 444 p.Metadata = meta 445 } 446 447 // Origin returns the origin value of the resource. 448 func (p *ProvisionTokenV2) Origin() string { 449 return p.Metadata.Origin() 450 } 451 452 // SetOrigin sets the origin value of the resource. 453 func (p *ProvisionTokenV2) SetOrigin(origin string) { 454 p.Metadata.SetOrigin(origin) 455 } 456 457 // GetSuggestedLabels returns the labels the resource should set when using this token 458 func (p *ProvisionTokenV2) GetSuggestedLabels() Labels { 459 return p.Spec.SuggestedLabels 460 } 461 462 // GetAgentMatcherLabels returns the set of labels that should be watched when an agent/service uses this token. 463 // An example of this is the Database Agent. 464 // When using the install-database.sh script, the script will add those labels as part of the `teleport.yaml` configuration. 465 // They are added to `db_service.resources.0.labels`. 466 func (p *ProvisionTokenV2) GetSuggestedAgentMatcherLabels() Labels { 467 return p.Spec.SuggestedAgentMatcherLabels 468 } 469 470 // V1 returns V1 version of the resource 471 func (p *ProvisionTokenV2) V1() *ProvisionTokenV1 { 472 return &ProvisionTokenV1{ 473 Roles: p.Spec.Roles, 474 Expires: p.Metadata.Expiry(), 475 Token: p.Metadata.Name, 476 } 477 } 478 479 // V2 returns V2 version of the resource 480 func (p *ProvisionTokenV2) V2() *ProvisionTokenV2 { 481 return p 482 } 483 484 // SetExpiry sets expiry time for the object 485 func (p *ProvisionTokenV2) SetExpiry(expires time.Time) { 486 p.Metadata.SetExpiry(expires) 487 } 488 489 // Expiry returns object expiry setting 490 func (p *ProvisionTokenV2) Expiry() time.Time { 491 return p.Metadata.Expiry() 492 } 493 494 // GetName returns the name of the provision token. This value can be secret! 495 // Use GetSafeName where the name may be logged. 496 func (p *ProvisionTokenV2) GetName() string { 497 return p.Metadata.Name 498 } 499 500 // SetName sets the name of the provision token. 501 func (p *ProvisionTokenV2) SetName(e string) { 502 p.Metadata.Name = e 503 } 504 505 // GetSafeName returns the name of the token, sanitized appropriately for 506 // join methods where the name is secret. This should be used when logging 507 // the token name. 508 func (p *ProvisionTokenV2) GetSafeName() string { 509 name := p.GetName() 510 if p.GetJoinMethod() != JoinMethodToken { 511 return name 512 } 513 514 // If the token name is short, we just blank the whole thing. 515 if len(name) < 16 { 516 return strings.Repeat("*", len(name)) 517 } 518 519 // If the token name is longer, we can show the last 25% of it to help 520 // the operator identify it. 521 hiddenBefore := int(0.75 * float64(len(name))) 522 name = name[hiddenBefore:] 523 name = strings.Repeat("*", hiddenBefore) + name 524 return name 525 } 526 527 // String returns the human readable representation of a provisioning token. 528 func (p ProvisionTokenV2) String() string { 529 expires := "never" 530 if !p.Expiry().IsZero() { 531 expires = p.Expiry().String() 532 } 533 return fmt.Sprintf("ProvisionToken(Roles=%v, Expires=%v)", p.Spec.Roles, expires) 534 } 535 536 // ProvisionTokensToV1 converts provision tokens to V1 list 537 func ProvisionTokensToV1(in []ProvisionToken) []ProvisionTokenV1 { 538 if in == nil { 539 return nil 540 } 541 out := make([]ProvisionTokenV1, len(in)) 542 for i := range in { 543 out[i] = *in[i].V1() 544 } 545 return out 546 } 547 548 // ProvisionTokensFromV1 converts V1 provision tokens to resource list 549 func ProvisionTokensFromV1(in []ProvisionTokenV1) []ProvisionToken { 550 if in == nil { 551 return nil 552 } 553 out := make([]ProvisionToken, len(in)) 554 for i := range in { 555 out[i] = in[i].V2() 556 } 557 return out 558 } 559 560 // V1 returns V1 version of the resource 561 func (p *ProvisionTokenV1) V1() *ProvisionTokenV1 { 562 return p 563 } 564 565 // V2 returns V2 version of the resource 566 func (p *ProvisionTokenV1) V2() *ProvisionTokenV2 { 567 t := &ProvisionTokenV2{ 568 Kind: KindToken, 569 Version: V2, 570 Metadata: Metadata{ 571 Name: p.Token, 572 Namespace: defaults.Namespace, 573 }, 574 Spec: ProvisionTokenSpecV2{ 575 Roles: p.Roles, 576 }, 577 } 578 if !p.Expires.IsZero() { 579 t.SetExpiry(p.Expires) 580 } 581 t.CheckAndSetDefaults() 582 return t 583 } 584 585 // String returns the human readable representation of a provisioning token. 586 func (p ProvisionTokenV1) String() string { 587 expires := "never" 588 if p.Expires.Unix() != 0 { 589 expires = p.Expires.String() 590 } 591 return fmt.Sprintf("ProvisionToken(Roles=%v, Expires=%v)", 592 p.Roles, expires) 593 } 594 595 func (a *ProvisionTokenSpecV2GitHub) checkAndSetDefaults() error { 596 if len(a.Allow) == 0 { 597 return trace.BadParameter("the %q join method requires at least one token allow rule", JoinMethodGitHub) 598 } 599 for _, rule := range a.Allow { 600 repoSet := rule.Repository != "" 601 ownerSet := rule.RepositoryOwner != "" 602 subSet := rule.Sub != "" 603 if !(subSet || ownerSet || repoSet) { 604 return trace.BadParameter( 605 `allow rule for %q must include at least one of "repository", "repository_owner" or "sub"`, 606 JoinMethodGitHub, 607 ) 608 } 609 } 610 if strings.Contains(a.EnterpriseServerHost, "/") { 611 return trace.BadParameter("'spec.github.enterprise_server_host' should not contain the scheme or path") 612 } 613 if a.EnterpriseServerHost != "" && a.EnterpriseSlug != "" { 614 return trace.BadParameter("'spec.github.enterprise_server_host' and `spec.github.enterprise_slug` cannot both be set") 615 } 616 return nil 617 } 618 619 func (a *ProvisionTokenSpecV2CircleCI) checkAndSetDefaults() error { 620 if len(a.Allow) == 0 { 621 return trace.BadParameter("the %q join method requires at least one token allow rule", JoinMethodCircleCI) 622 } 623 if a.OrganizationID == "" { 624 return trace.BadParameter("the %q join method requires 'organization_id' to be set", JoinMethodCircleCI) 625 } 626 for _, rule := range a.Allow { 627 projectSet := rule.ProjectID != "" 628 contextSet := rule.ContextID != "" 629 if !projectSet && !contextSet { 630 return trace.BadParameter( 631 `allow rule for %q must include at least "project_id" or "context_id"`, 632 JoinMethodCircleCI, 633 ) 634 } 635 } 636 return nil 637 } 638 639 func (a *ProvisionTokenSpecV2Kubernetes) checkAndSetDefaults() error { 640 if len(a.Allow) == 0 { 641 return trace.BadParameter("allow: at least one rule must be set") 642 } 643 for i, allowRule := range a.Allow { 644 if allowRule.ServiceAccount == "" { 645 return trace.BadParameter( 646 "allow[%d].service_account: name of service account must be set", 647 i, 648 ) 649 } 650 if len(strings.Split(allowRule.ServiceAccount, ":")) != 2 { 651 return trace.BadParameter( 652 `allow[%d].service_account: name of service account should be in format "namespace:service_account", got %q instead`, 653 i, 654 allowRule.ServiceAccount, 655 ) 656 } 657 } 658 659 if a.Type == KubernetesJoinTypeUnspecified { 660 // For compatibility with older resources which did not have a Type 661 // field we default to "in_cluster". 662 a.Type = KubernetesJoinTypeInCluster 663 } 664 switch a.Type { 665 case KubernetesJoinTypeInCluster: 666 if a.StaticJWKS != nil { 667 return trace.BadParameter("static_jwks: must not be set when type is %q", KubernetesJoinTypeInCluster) 668 } 669 case KubernetesJoinTypeStaticJWKS: 670 if a.StaticJWKS == nil { 671 return trace.BadParameter("static_jwks: must be set when type is %q", KubernetesJoinTypeStaticJWKS) 672 } 673 if a.StaticJWKS.JWKS == "" { 674 return trace.BadParameter("static_jwks.jwks: must be set when type is %q", KubernetesJoinTypeStaticJWKS) 675 } 676 default: 677 return trace.BadParameter( 678 "type: must be one of (%s), got %q", 679 apiutils.JoinStrings(JoinMethods, ", "), 680 a.Type, 681 ) 682 } 683 684 return nil 685 } 686 687 func (a *ProvisionTokenSpecV2Azure) checkAndSetDefaults() error { 688 if len(a.Allow) == 0 { 689 return trace.BadParameter( 690 "the %q join method requires defined azure allow rules", 691 JoinMethodAzure, 692 ) 693 } 694 for _, allowRule := range a.Allow { 695 if allowRule.Subscription == "" { 696 return trace.BadParameter( 697 "the %q join method requires azure allow rules with non-empty subscription", 698 JoinMethodAzure, 699 ) 700 } 701 } 702 return nil 703 } 704 705 const defaultGitLabDomain = "gitlab.com" 706 707 func (a *ProvisionTokenSpecV2GitLab) checkAndSetDefaults() error { 708 if len(a.Allow) == 0 { 709 return trace.BadParameter( 710 "the %q join method requires defined gitlab allow rules", 711 JoinMethodGitLab, 712 ) 713 } 714 for _, allowRule := range a.Allow { 715 if allowRule.Sub == "" && allowRule.NamespacePath == "" && allowRule.ProjectPath == "" && allowRule.CIConfigRefURI == "" { 716 return trace.BadParameter( 717 "the %q join method requires allow rules with at least one of ['sub', 'project_path', 'namespace_path', 'ci_config_ref_uri'] to ensure security.", 718 JoinMethodGitLab, 719 ) 720 } 721 } 722 723 if a.Domain == "" { 724 a.Domain = defaultGitLabDomain 725 } else { 726 if strings.Contains(a.Domain, "/") { 727 return trace.BadParameter( 728 "'spec.gitlab.domain' should not contain the scheme or path", 729 ) 730 } 731 } 732 return nil 733 } 734 735 func (a *ProvisionTokenSpecV2GCP) checkAndSetDefaults() error { 736 if len(a.Allow) == 0 { 737 return trace.BadParameter("the %q join method requires at least one token allow rule", JoinMethodGCP) 738 } 739 for _, allowRule := range a.Allow { 740 if len(allowRule.ProjectIDs) == 0 { 741 return trace.BadParameter( 742 "the %q join method requires gcp allow rules with at least one project ID", 743 JoinMethodGCP, 744 ) 745 } 746 } 747 return nil 748 } 749 750 func (a *ProvisionTokenSpecV2Spacelift) checkAndSetDefaults() error { 751 if a.Hostname == "" { 752 return trace.BadParameter( 753 "hostname: should be set to the hostname of the spacelift tenant", 754 ) 755 } 756 if strings.Contains(a.Hostname, "/") { 757 return trace.BadParameter( 758 "hostname: should not contain the scheme or path", 759 ) 760 } 761 if len(a.Allow) == 0 { 762 return trace.BadParameter("allow: at least one rule must be set") 763 } 764 for i, allowRule := range a.Allow { 765 if allowRule.SpaceID == "" && allowRule.CallerID == "" { 766 return trace.BadParameter( 767 "allow[%d]: at least one of ['space_id', 'caller_id'] must be set", 768 i, 769 ) 770 } 771 } 772 return nil 773 } 774 775 func (a *ProvisionTokenSpecV2TPM) validate() error { 776 for i, caData := range a.EKCertAllowedCAs { 777 p, _ := pem.Decode([]byte(caData)) 778 if p == nil { 779 return trace.BadParameter( 780 "ekcert_allowed_cas[%d]: no pem block found", 781 i, 782 ) 783 } 784 if p.Type != "CERTIFICATE" { 785 return trace.BadParameter( 786 "ekcert_allowed_cas[%d]: pem block is not 'CERTIFICATE' type", 787 i, 788 ) 789 } 790 if _, err := x509.ParseCertificate(p.Bytes); err != nil { 791 return trace.Wrap( 792 err, 793 "ekcert_allowed_cas[%d]: parsing certificate", 794 i, 795 ) 796 797 } 798 } 799 800 if len(a.Allow) == 0 { 801 return trace.BadParameter( 802 "allow: at least one rule must be set", 803 ) 804 } 805 for i, allowRule := range a.Allow { 806 if len(allowRule.EKPublicHash) == 0 && len(allowRule.EKCertificateSerial) == 0 { 807 return trace.BadParameter( 808 "allow[%d]: at least one of ['ek_public_hash', 'ek_certificate_serial'] must be set", 809 i, 810 ) 811 } 812 } 813 return nil 814 }