19
19
package web
20
20
21
21
import (
22
- "bytes"
23
22
"context"
24
23
"encoding/hex"
25
24
"fmt"
26
25
"hash/fnv"
27
26
"net/http"
28
27
"net/url"
29
28
"reflect"
30
- "regexp"
31
29
"sort"
32
- "strconv"
33
30
"strings"
34
31
"time"
35
32
36
- "github.com/google/safetext/shsprintf"
37
33
"github.com/google/uuid"
38
34
"github.com/gravitational/trace"
39
35
"github.com/julienschmidt/httprouter"
40
- "k8s.io/apimachinery/pkg/util/validation"
41
36
42
- "github.com/gravitational/teleport"
43
37
"github.com/gravitational/teleport/api/client/proto"
44
38
"github.com/gravitational/teleport/api/types"
45
39
apiutils "github.com/gravitational/teleport/api/utils"
46
40
"github.com/gravitational/teleport/lib/defaults"
47
41
"github.com/gravitational/teleport/lib/httplib"
48
- "github.com/gravitational/teleport/lib/modules"
49
42
"github.com/gravitational/teleport/lib/services"
50
43
"github.com/gravitational/teleport/lib/tlsca"
51
44
"github.com/gravitational/teleport/lib/ui"
@@ -55,8 +48,7 @@ import (
55
48
)
56
49
57
50
const (
58
- stableCloudChannelRepo = "stable/cloud"
59
- HeaderTokenName = "X-Teleport-TokenName"
51
+ HeaderTokenName = "X-Teleport-TokenName"
60
52
)
61
53
62
54
// nodeJoinToken contains node token fields for the UI.
@@ -80,15 +72,9 @@ type scriptSettings struct {
80
72
appURI string
81
73
joinMethod string
82
74
databaseInstallMode bool
83
- installUpdater bool
84
75
85
76
discoveryInstallMode bool
86
77
discoveryGroup string
87
-
88
- // automaticUpgradesVersion is the target automatic upgrades version.
89
- // The version must be valid semver, with the leading 'v'. e.g. v15.0.0-dev
90
- // Required when installUpdater is true.
91
- automaticUpgradesVersion string
92
78
}
93
79
94
80
// automaticUpgrades returns whether automaticUpgrades should be enabled.
@@ -377,41 +363,16 @@ func (h *Handler) createTokenForDiscoveryHandle(w http.ResponseWriter, r *http.R
377
363
}, nil
378
364
}
379
365
380
- // getAutoUpgrades checks if automaticUpgrades are enabled and returns the
381
- // version that should be used according to auto upgrades default channel.
382
- // If something bad happens, the error is logged and the function falls back to
383
- // the process Teleport version.
384
- func (h * Handler ) getAutoUpgrades (ctx context.Context ) (bool , string ) {
385
- var autoUpgradesVersion string
386
- var err error
387
- autoUpgrades := automaticUpgrades (h .GetClusterFeatures ())
388
- if autoUpgrades {
389
- const group , updaterUUID = "" , ""
390
- autoUpgradesVersion , err = h .autoUpdateAgentVersion (ctx , group , updaterUUID )
391
- if err != nil {
392
- h .logger .WarnContext (ctx , "Failed to get auto upgrades version, falling back to self version." , "error" , err )
393
- return autoUpgrades , teleport .Version
394
- }
395
- autoUpgradesVersion = fmt .Sprintf ("v%s" , autoUpgradesVersion )
396
- }
397
- return autoUpgrades , autoUpgradesVersion
398
-
399
- }
400
-
401
366
func (h * Handler ) getNodeJoinScriptHandle (w http.ResponseWriter , r * http.Request , params httprouter.Params ) (interface {}, error ) {
402
367
httplib .SetScriptHeaders (w .Header ())
403
368
404
- autoUpgrades , autoUpgradesVersion := h .getAutoUpgrades (r .Context ())
405
-
406
369
settings := scriptSettings {
407
- token : params .ByName ("token" ),
408
- appInstallMode : false ,
409
- joinMethod : r .URL .Query ().Get ("method" ),
410
- installUpdater : autoUpgrades ,
411
- automaticUpgradesVersion : autoUpgradesVersion ,
370
+ token : params .ByName ("token" ),
371
+ appInstallMode : false ,
372
+ joinMethod : r .URL .Query ().Get ("method" ),
412
373
}
413
374
414
- script , err := getJoinScript (r .Context (), settings , h . GetProxyClient () )
375
+ script , err := h . getJoinScript (r .Context (), settings )
415
376
if err != nil {
416
377
h .logger .InfoContext (r .Context (), "Failed to return the node install script" , "error" , err )
417
378
w .Write (scripts .ErrorBashScript )
@@ -451,18 +412,14 @@ func (h *Handler) getAppJoinScriptHandle(w http.ResponseWriter, r *http.Request,
451
412
return nil , nil
452
413
}
453
414
454
- autoUpgrades , autoUpgradesVersion := h .getAutoUpgrades (r .Context ())
455
-
456
415
settings := scriptSettings {
457
- token : params .ByName ("token" ),
458
- appInstallMode : true ,
459
- appName : name ,
460
- appURI : uri ,
461
- installUpdater : autoUpgrades ,
462
- automaticUpgradesVersion : autoUpgradesVersion ,
416
+ token : params .ByName ("token" ),
417
+ appInstallMode : true ,
418
+ appName : name ,
419
+ appURI : uri ,
463
420
}
464
421
465
- script , err := getJoinScript (r .Context (), settings , h . GetProxyClient () )
422
+ script , err := h . getJoinScript (r .Context (), settings )
466
423
if err != nil {
467
424
h .logger .InfoContext (r .Context (), "Failed to return the app install script" , "error" , err )
468
425
w .Write (scripts .ErrorBashScript )
@@ -481,16 +438,12 @@ func (h *Handler) getAppJoinScriptHandle(w http.ResponseWriter, r *http.Request,
481
438
func (h * Handler ) getDatabaseJoinScriptHandle (w http.ResponseWriter , r * http.Request , params httprouter.Params ) (interface {}, error ) {
482
439
httplib .SetScriptHeaders (w .Header ())
483
440
484
- autoUpgrades , autoUpgradesVersion := h .getAutoUpgrades (r .Context ())
485
-
486
441
settings := scriptSettings {
487
- token : params .ByName ("token" ),
488
- databaseInstallMode : true ,
489
- installUpdater : autoUpgrades ,
490
- automaticUpgradesVersion : autoUpgradesVersion ,
442
+ token : params .ByName ("token" ),
443
+ databaseInstallMode : true ,
491
444
}
492
445
493
- script , err := getJoinScript (r .Context (), settings , h . GetProxyClient () )
446
+ script , err := h . getJoinScript (r .Context (), settings )
494
447
if err != nil {
495
448
h .logger .InfoContext (r .Context (), "Failed to return the database install script" , "error" , err )
496
449
w .Write (scripts .ErrorBashScript )
@@ -511,8 +464,6 @@ func (h *Handler) getDiscoveryJoinScriptHandle(w http.ResponseWriter, r *http.Re
511
464
queryValues := r .URL .Query ()
512
465
const discoveryGroupQueryParam = "discoveryGroup"
513
466
514
- autoUpgrades , autoUpgradesVersion := h .getAutoUpgrades (r .Context ())
515
-
516
467
discoveryGroup , err := url .QueryUnescape (queryValues .Get (discoveryGroupQueryParam ))
517
468
if err != nil {
518
469
h .logger .DebugContext (r .Context (), "Failed to return the discovery install script" ,
@@ -531,14 +482,12 @@ func (h *Handler) getDiscoveryJoinScriptHandle(w http.ResponseWriter, r *http.Re
531
482
}
532
483
533
484
settings := scriptSettings {
534
- token : params .ByName ("token" ),
535
- discoveryInstallMode : true ,
536
- discoveryGroup : discoveryGroup ,
537
- installUpdater : autoUpgrades ,
538
- automaticUpgradesVersion : autoUpgradesVersion ,
485
+ token : params .ByName ("token" ),
486
+ discoveryInstallMode : true ,
487
+ discoveryGroup : discoveryGroup ,
539
488
}
540
489
541
- script , err := getJoinScript (r .Context (), settings , h . GetProxyClient () )
490
+ script , err := h . getJoinScript (r .Context (), settings )
542
491
if err != nil {
543
492
h .logger .InfoContext (r .Context (), "Failed to return the discovery install script" , "error" , err )
544
493
w .Write (scripts .ErrorBashScript )
@@ -554,8 +503,9 @@ func (h *Handler) getDiscoveryJoinScriptHandle(w http.ResponseWriter, r *http.Re
554
503
return nil , nil
555
504
}
556
505
557
- func getJoinScript (ctx context.Context , settings scriptSettings , m nodeAPIGetter ) (string , error ) {
558
- switch types .JoinMethod (settings .joinMethod ) {
506
+ func (h * Handler ) getJoinScript (ctx context.Context , settings scriptSettings ) (string , error ) {
507
+ joinMethod := types .JoinMethod (settings .joinMethod )
508
+ switch joinMethod {
559
509
case types .JoinMethodUnspecified , types .JoinMethodToken :
560
510
if err := validateJoinToken (settings .token ); err != nil {
561
511
return "" , trace .Wrap (err )
@@ -565,141 +515,55 @@ func getJoinScript(ctx context.Context, settings scriptSettings, m nodeAPIGetter
565
515
return "" , trace .BadParameter ("join method %q is not supported via script" , settings .joinMethod )
566
516
}
567
517
518
+ clt := h .GetProxyClient ()
519
+
568
520
// The provided token can be attacker controlled, so we must validate
569
521
// it with the backend before using it to generate the script.
570
- token , err := m .GetToken (ctx , settings .token )
522
+ token , err := clt .GetToken (ctx , settings .token )
571
523
if err != nil {
572
524
return "" , trace .BadParameter ("invalid token" )
573
525
}
574
526
575
- // Get hostname and port from proxy server address.
576
- proxyServers , err := m .GetProxies ()
577
- if err != nil {
578
- return "" , trace .Wrap (err )
579
- }
580
-
581
- if len (proxyServers ) == 0 {
582
- return "" , trace .NotFound ("no proxy servers found" )
583
- }
584
-
585
- version := proxyServers [0 ].GetTeleportVersion ()
586
-
587
- publicAddr := proxyServers [0 ].GetPublicAddr ()
588
- if publicAddr == "" {
589
- return "" , trace .Errorf ("proxy public_addr is not set, you must set proxy_service.public_addr to the publicly reachable address of the proxy before you can generate a node join script" )
590
- }
591
-
592
- hostname , portStr , err := utils .SplitHostPort (publicAddr )
593
- if err != nil {
594
- return "" , trace .Wrap (err )
595
- }
527
+ // TODO(hugoShaka): hit the local accesspoint which has a cache instead of asking the auth every time.
596
528
597
529
// Get the CA pin hashes of the cluster to join.
598
- localCAResponse , err := m .GetClusterCACert (ctx )
530
+ localCAResponse , err := clt .GetClusterCACert (ctx )
599
531
if err != nil {
600
532
return "" , trace .Wrap (err )
601
533
}
534
+
602
535
caPins , err := tlsca .CalculatePins (localCAResponse .TLSCA )
603
536
if err != nil {
604
537
return "" , trace .Wrap (err )
605
538
}
606
539
607
- labelsList := []string {}
608
- for labelKey , labelValues := range token .GetSuggestedLabels () {
609
- labels := strings .Join (labelValues , " " )
610
- labelsList = append (labelsList , fmt .Sprintf ("%s=%s" , labelKey , labels ))
611
- }
612
-
613
- var dbServiceResourceLabels []string
614
- if settings .databaseInstallMode {
615
- suggestedAgentMatcherLabels := token .GetSuggestedAgentMatcherLabels ()
616
- dbServiceResourceLabels , err = scripts .MarshalLabelsYAML (suggestedAgentMatcherLabels , 6 )
617
- if err != nil {
618
- return "" , trace .Wrap (err )
619
- }
620
- }
621
-
622
- var buf bytes.Buffer
623
- var appServerResourceLabels []string
624
- // If app install mode is requested but parameters are blank for some reason,
625
- // we need to return an error.
626
- if settings .appInstallMode {
627
- if errs := validation .IsDNS1035Label (settings .appName ); len (errs ) > 0 {
628
- return "" , trace .BadParameter ("appName %q must be a valid DNS subdomain: https://goteleport.com/docs/enroll-resources/application-access/guides/connecting-apps/#application-name" , settings .appName )
629
- }
630
- if ! appURIPattern .MatchString (settings .appURI ) {
631
- return "" , trace .BadParameter ("appURI %q contains invalid characters" , settings .appURI )
632
- }
633
-
634
- suggestedLabels := token .GetSuggestedLabels ()
635
- appServerResourceLabels , err = scripts .MarshalLabelsYAML (suggestedLabels , 4 )
636
- if err != nil {
637
- return "" , trace .Wrap (err )
638
- }
639
- }
640
-
641
- if settings .discoveryInstallMode {
642
- if settings .discoveryGroup == "" {
643
- return "" , trace .BadParameter ("discovery group is required" )
644
- }
645
- }
646
-
647
- packageName := types .PackageNameOSS
648
- if modules .GetModules ().BuildType () == modules .BuildEnterprise {
649
- packageName = types .PackageNameEnt
650
- }
651
-
652
- // By default, it will use `stable/v<majorVersion>`, eg stable/v12
653
- repoChannel := ""
654
-
655
- // The install script will install the updater (teleport-ent-updater) for Cloud customers enrolled in Automatic Upgrades.
656
- // The repo channel used must be `stable/cloud` which has the available packages for the Cloud Customer's agents.
657
- // It pins the teleport version to the one specified by the default version channel
658
- // This ensures the initial installed version is the same as the `teleport-ent-updater` would install.
659
- if settings .installUpdater {
660
- if settings .automaticUpgradesVersion == "" {
661
- return "" , trace .Wrap (err , "automatic upgrades version must be set when installUpdater is true" )
662
- }
663
-
664
- repoChannel = stableCloudChannelRepo
665
- // automaticUpgradesVersion has vX.Y.Z format, however the script
666
- // expects the version to not include the `v` so we strip it
667
- version = strings .TrimPrefix (settings .automaticUpgradesVersion , "v" )
668
- }
669
-
670
- // This section relies on Go's default zero values to make sure that the settings
671
- // are correct when not installing an app.
672
- err = scripts .InstallNodeBashScript .Execute (& buf , map [string ]interface {}{
673
- "token" : settings .token ,
674
- "hostname" : hostname ,
675
- "port" : portStr ,
676
- // The install.sh script has some manually generated configs and some
677
- // generated by the `teleport <service> config` commands. The old bash
678
- // version used space delimited values whereas the teleport command uses
679
- // a comma delimeter. The Old version can be removed when the install.sh
680
- // file has been completely converted over.
681
- "caPinsOld" : strings .Join (caPins , " " ),
682
- "caPins" : strings .Join (caPins , "," ),
683
- "packageName" : packageName ,
684
- "repoChannel" : repoChannel ,
685
- "installUpdater" : strconv .FormatBool (settings .installUpdater ),
686
- "version" : shsprintf .EscapeDefaultContext (version ),
687
- "appInstallMode" : strconv .FormatBool (settings .appInstallMode ),
688
- "appServerResourceLabels" : appServerResourceLabels ,
689
- "appName" : shsprintf .EscapeDefaultContext (settings .appName ),
690
- "appURI" : shsprintf .EscapeDefaultContext (settings .appURI ),
691
- "joinMethod" : shsprintf .EscapeDefaultContext (settings .joinMethod ),
692
- "labels" : strings .Join (labelsList , "," ),
693
- "databaseInstallMode" : strconv .FormatBool (settings .databaseInstallMode ),
694
- "db_service_resource_labels" : dbServiceResourceLabels ,
695
- "discoveryInstallMode" : settings .discoveryInstallMode ,
696
- "discoveryGroup" : shsprintf .EscapeDefaultContext (settings .discoveryGroup ),
697
- })
540
+ installOpts , err := h .installScriptOptions (ctx )
698
541
if err != nil {
699
- return "" , trace .Wrap (err )
700
- }
701
-
702
- return buf .String (), nil
542
+ return "" , trace .Wrap (err , "Building install script options" )
543
+ }
544
+
545
+ nodeInstallOpts := scripts.InstallNodeScriptOptions {
546
+ InstallOptions : installOpts ,
547
+ Token : token .GetName (),
548
+ CAPins : caPins ,
549
+ // We are using the joinMethod from the script settings instead of the one from the token
550
+ // to reproduce the previous script behavior. I'm also afraid that using the
551
+ // join method from the token would provide an oracle for an attacker wanting to discover
552
+ // the join method.
553
+ // We might want to change this in the future to lookup the join method from the token
554
+ // to avoid potential mismatch and allow the caller to not care about the join method.
555
+ JoinMethod : joinMethod ,
556
+ Labels : token .GetSuggestedLabels (),
557
+ LabelMatchers : token .GetSuggestedAgentMatcherLabels (),
558
+ AppServiceEnabled : settings .appInstallMode ,
559
+ AppName : settings .appName ,
560
+ AppURI : settings .appURI ,
561
+ DatabaseServiceEnabled : settings .databaseInstallMode ,
562
+ DiscoveryServiceEnabled : settings .discoveryInstallMode ,
563
+ DiscoveryGroup : settings .discoveryGroup ,
564
+ }
565
+
566
+ return scripts .GetNodeInstallScript (ctx , nodeInstallOpts )
703
567
}
704
568
705
569
// validateJoinToken validate a join token.
@@ -789,17 +653,3 @@ func isSameAzureRuleSet(r1, r2 []*types.ProvisionTokenSpecV2Azure_Rule) bool {
789
653
sortAzureRules (r2 )
790
654
return reflect .DeepEqual (r1 , r2 )
791
655
}
792
-
793
- type nodeAPIGetter interface {
794
- // GetToken looks up a provisioning token.
795
- GetToken (ctx context.Context , token string ) (types.ProvisionToken , error )
796
-
797
- // GetClusterCACert returns the CAs for the local cluster without signing keys.
798
- GetClusterCACert (ctx context.Context ) (* proto.GetClusterCACertResponse , error )
799
-
800
- // GetProxies returns a list of registered proxies.
801
- GetProxies () ([]types.Server , error )
802
- }
803
-
804
- // appURIPattern is a regexp excluding invalid characters from application URIs.
805
- var appURIPattern = regexp .MustCompile (`^[-\w/:. ]+$` )
0 commit comments