github.com/defang-io/defang/src@v0.0.0-20240505002154-bdf411911834/pkg/cli/connect.go (about) 1 package cli 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "errors" 8 "net" 9 "strings" 10 11 "github.com/defang-io/defang/src/pkg/cli/client/byoc/clouds" 12 "github.com/defang-io/defang/src/pkg/term" 13 14 "github.com/defang-io/defang/src/pkg/cli/client" 15 "github.com/defang-io/defang/src/pkg/types" 16 ) 17 18 const DefaultCluster = "fabric-prod1.defang.dev" 19 20 // Deprecated: should use grpc to get the tenant ID 21 func GetTenantID(cluster string) types.TenantID { 22 if tenantId, _ := SplitTenantHost(cluster); tenantId != types.DEFAULT_TENANT { 23 return tenantId 24 } 25 26 _, tenantId := getExistingTokenAndTenant(cluster) 27 return tenantId 28 } 29 30 func SplitTenantHost(cluster string) (types.TenantID, string) { 31 tenant := types.DEFAULT_TENANT 32 parts := strings.SplitN(cluster, "@", 2) 33 if len(parts) == 2 { 34 tenant, cluster = types.TenantID(parts[0]), parts[1] 35 } 36 if cluster == "" { 37 cluster = DefaultCluster 38 } 39 if _, _, err := net.SplitHostPort(cluster); err != nil { 40 cluster = cluster + ":443" // default to https 41 } 42 return tenant, cluster 43 } 44 45 func getExistingTokenAndTenant(cluster string) (string, types.TenantID) { 46 var tenantId types.TenantID 47 accessToken := GetExistingToken(cluster) 48 if accessToken != "" { 49 // HACK: don't rely on info in token 50 tenantId, _, _ = tenantFromAccessToken(accessToken) 51 } 52 return accessToken, tenantId 53 } 54 55 func Connect(cluster string, loader client.ProjectLoader) (*client.GrpcClient, types.TenantID) { 56 accessToken, tenantId := getExistingTokenAndTenant(cluster) 57 58 tenant, host := SplitTenantHost(cluster) 59 if tenant != types.DEFAULT_TENANT { 60 tenantId = tenant 61 } 62 term.Debug(" - Using tenant", tenantId, "for cluster", host) 63 64 defangClient := client.NewGrpcClient(host, accessToken, tenantId, loader) 65 resp, err := defangClient.WhoAmI(context.TODO()) // TODO: Should we pass in the command context? 66 if err != nil { 67 term.Debug(" - Unable to validate tenant ID with server:", err) 68 } 69 if resp != nil && tenantId != types.TenantID(resp.Tenant) { 70 term.Warnf(" ! Overriding locally cached TenantID %v with server provided value %v", tenantId, resp.Tenant) 71 tenantId = types.TenantID(resp.Tenant) 72 } 73 return defangClient, tenantId 74 } 75 76 func NewClient(cluster string, provider client.Provider, loader client.ProjectLoader) client.Client { 77 defangClient, tenantId := Connect(cluster, loader) 78 79 if provider == client.ProviderAWS { 80 term.Info(" # Using AWS provider") // HACK: # prevents errors when evaluating the shell completion script 81 byocClient := clouds.NewByocAWS(tenantId, defangClient) 82 return byocClient 83 } 84 85 return defangClient 86 } 87 88 // Deprecated: don't rely on info in token 89 func tenantFromAccessToken(at string) (types.TenantID, string, error) { 90 parts := strings.Split(at, ".") 91 if len(parts) != 3 { 92 return "", "", errors.New("not a JWT") 93 } 94 var claims struct { 95 Iss string `json:"iss"` 96 Sub string `json:"sub"` 97 } 98 bytes, err := base64.RawURLEncoding.DecodeString(parts[1]) 99 if err != nil { 100 return "", "", err 101 } 102 err = json.Unmarshal(bytes, &claims) 103 return types.TenantID(claims.Sub), claims.Iss, err 104 }