Skip to content

Commit

Permalink
Add provision to update the CSP issuer to TCSP
Browse files Browse the repository at this point in the history
- Add provision through central configuration to update the CSP issuer from VCSP to TCSP. Also added an option in central configuration so that CLI can react to the configuration flag set in central configuration to update the issuers in the already created contexts.

Signed-off-by: Prem Kumar Kalle <[email protected]>
  • Loading branch information
prkalle authored and anujc25 committed Jul 18, 2024
1 parent 29058eb commit 2f71ea2
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 29 deletions.
127 changes: 122 additions & 5 deletions pkg/auth/csp/tanzu.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

"github.com/vmware-tanzu/tanzu-cli/pkg/centralconfig"
cliconfig "github.com/vmware-tanzu/tanzu-cli/pkg/config"
"github.com/vmware-tanzu/tanzu-cli/pkg/constants"
"github.com/vmware-tanzu/tanzu-plugin-runtime/config"
"github.com/vmware-tanzu/tanzu-plugin-runtime/log"
)
Expand All @@ -41,9 +42,13 @@ const (
defaultListenAddress = "127.0.0.1:0"
defaultCallbackPath = "/callback"

centralConfigTanzuHubMetadata = "cli.core.tanzu_hub_metadata"
defaultCSPDisplayName = "Tanzu Platform"
defaultCSPProductIdentifier = "TANZU-SAAS"
centralConfigTanzuHubMetadata = "cli.core.tanzu_hub_metadata"
centralConfigTanzuDefaultCSPMetadata = "cli.core.tanzu_default_csp_metadata"
centralConfigTanzuTCSPMetadata = "cli.core.tanzu_tcsp_metadata"
centralConfigTanzuKnownIssersEndpoints = "cli.core.tanzu_csp_known_issuer_endpoints"
centralConfigCLIConfigCSPIssuerUpdateFlag = "cli.core.tanzu_cli_config_csp_issuer_update_flag"
defaultCSPDisplayName = "Tanzu Platform"
defaultCSPProductIdentifier = "TANZU-SAAS"
)

// stdin returns the file descriptor for stdin as an int.
Expand All @@ -63,6 +68,19 @@ type tanzuHubMetadata struct {
UseCentralConfig bool `json:"useCentralConfig" yaml:"useCentralConfig"`
}

type issuerEndPoints struct {
AuthURL string `json:"authURL" yaml:"authURL"`
TokenURL string `json:"tokenURL" yaml:"tokenURL"`
}

type cspKnownIssuerEndpoints map[string]issuerEndPoints

// TanzuCSPMetadata to parse the CSP metadata from central config
type TanzuCSPMetadata struct {
IssuerProduction string `json:"issuerProduction" yaml:"issuerProduction"`
IssuerStaging string `json:"issuerStaging" yaml:"issuerStaging"`
}

type cspLoginHandler struct {
tokenExchange context.Context
tokenExchangeComplete context.CancelFunc
Expand Down Expand Up @@ -157,12 +175,13 @@ func TanzuLogin(issuerURL string, opts ...LoginOption) (*Token, error) {
promptForValue: promptForValue,
isTTY: term.IsTerminal,
}
cspKnownIssuerEPs := getCSPKnownIssuersEndpoints()
h.oauthConfig = &oauth2.Config{
RedirectURL: (&url.URL{Scheme: "http", Host: h.listenAddr, Path: h.callbackPath}).String(),
ClientID: h.clientID,
Endpoint: oauth2.Endpoint{
AuthURL: KnownIssuers[issuerURL].AuthURL,
TokenURL: KnownIssuers[issuerURL].TokenURL,
AuthURL: cspKnownIssuerEPs[issuerURL].AuthURL,
TokenURL: cspKnownIssuerEPs[issuerURL].TokenURL,
},
}
for _, opt := range opts {
Expand Down Expand Up @@ -555,3 +574,101 @@ func getTanzuHubMetadataFromCentralConfig() tanzuHubMetadata {
_ = centralConfigReader.GetCentralConfigEntry(centralConfigTanzuHubMetadata, &hubMetadata)
return hubMetadata
}

// GetCSPMetadata gets the CSP metadata from central config as best effort,
// If it fails to get the metadata from central config, it returns the default values
func GetCSPMetadata() TanzuCSPMetadata {
cspMetaData := getCSPMetadataFromCentralConfig()
// If failed to get the CSP Metadata from central config,
// set the default Issuer URL of VCSP
// TODO(prkalle): Update the default Issuers to TCSP issuer URL( If TCSP is not stable in current release, update it next release)
// This just defaults in the code, defaults in central config can be updated anytime
if cspMetaData.IssuerStaging == "" {
cspMetaData.IssuerStaging = StgIssuer
}
if cspMetaData.IssuerProduction == "" {
cspMetaData.IssuerProduction = ProdIssuer
}

return cspMetaData
}

// getCSPMetadataFromCentralConfig gets the CSP metadata from central config as best effort
func getCSPMetadataFromCentralConfig() TanzuCSPMetadata {
cspMetadata := TanzuCSPMetadata{}

// We will get the central configuration from the default discovery source
discoverySource, err := config.GetCLIDiscoverySource(cliconfig.DefaultStandaloneDiscoveryName)
if err != nil {
return cspMetadata
}
centralConfigReader := centralconfig.NewCentralConfigReader(discoverySource)

// Get the tanzu CSP metadata based on user preference
useTanzuCSP, _ := strconv.ParseBool(os.Getenv(constants.UseTanzuCSP))
if useTanzuCSP {
_ = centralConfigReader.GetCentralConfigEntry(centralConfigTanzuTCSPMetadata, &cspMetadata)
} else {
_ = centralConfigReader.GetCentralConfigEntry(centralConfigTanzuDefaultCSPMetadata, &cspMetadata)
}

return cspMetadata
}

// getCSPKnownIssuersEndpoints gets the CSP known issuer endpoints from central config as best effort,
// If it fails to fetch data from central config, it returns the default values
func getCSPKnownIssuersEndpoints() cspKnownIssuerEndpoints {
cspKnownIssuerEPs, err := getCSPKnownEndpointsFromCentralConfig()
if err == nil {
return cspKnownIssuerEPs
}

// If failed to get the CSP Known Issuer endpoints, use the defaults
for key := range DefaultKnownIssuers {
cspKnownIssuerEPs[key] = issuerEndPoints{
AuthURL: DefaultKnownIssuers[key].AuthURL,
TokenURL: DefaultKnownIssuers[key].TokenURL,
}
}

return cspKnownIssuerEPs
}

// getCSPKnownEndpointsFromCentralConfig gets the CSP known endpoints from central config as best effort
func getCSPKnownEndpointsFromCentralConfig() (cspKnownIssuerEndpoints, error) {
cspKnownIssuerEPs := cspKnownIssuerEndpoints{}

// We will get the central configuration from the default discovery source
discoverySource, err := config.GetCLIDiscoverySource(cliconfig.DefaultStandaloneDiscoveryName)
if err != nil {
return cspKnownIssuerEPs, err
}
centralConfigReader := centralconfig.NewCentralConfigReader(discoverySource)

err = centralConfigReader.GetCentralConfigEntry(centralConfigTanzuKnownIssersEndpoints, &cspKnownIssuerEPs)
if err != nil {
return cspKnownIssuerEPs, err
}

return cspKnownIssuerEPs, nil
}

// GetIssuerUpdateFlagFromCentralConfig gets the issuer update flag (used to update the CLI config file)
// from Central config as best effort
func GetIssuerUpdateFlagFromCentralConfig() bool {
updateIssuerInCLIConfig := false

// We will get the central configuration from the default discovery source
discoverySource, err := config.GetCLIDiscoverySource(cliconfig.DefaultStandaloneDiscoveryName)
if err != nil {
return updateIssuerInCLIConfig
}
centralConfigReader := centralconfig.NewCentralConfigReader(discoverySource)

err = centralConfigReader.GetCentralConfigEntry(centralConfigCLIConfigCSPIssuerUpdateFlag, &updateIssuerInCLIConfig)
if err != nil {
return updateIssuerInCLIConfig
}

return updateIssuerInCLIConfig
}
35 changes: 28 additions & 7 deletions pkg/auth/csp/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,18 @@ const (
// ExtraIDToken is the key in the Extra fields map that contains id_token.
ExtraIDToken = "id_token"

// StgIssuer is the CSP staging issuer.
// StgIssuer is the VMware CSP(VCSP) staging issuer.
StgIssuer = "https://console-stg.cloud.vmware.com/csp/gateway/am/api"

// ProdIssuer is the CSP issuer.
// ProdIssuer is the VMware CSP(VCSP) issuer.
ProdIssuer = "https://console.cloud.vmware.com/csp/gateway/am/api"

// StgIssuerTCSP is the Tanzu CSP (TCSP) staging issuer.
StgIssuerTCSP = "https://console-stg.tanzu.broadcom.com/csp/gateway/am/api"

// ProdIssuerTCSP is the Tanzu CSP (TCSP) issuer
ProdIssuerTCSP = "https://console.tanzu.broadcom.com/csp/gateway/am/api"

//nolint:gosec // Avoid "hardcoded credentials" false positive.
// APITokenKey is the env var for an API token override.
APITokenKey = "CSP_API_TOKEN"
Expand All @@ -51,8 +57,8 @@ const (
)

var (
// KnownIssuers are known OAuth2 endpoints in each CSP environment.
KnownIssuers = map[string]oauth2.Endpoint{
// DefaultKnownIssuers are known OAuth2 endpoints in each CSP environment.
DefaultKnownIssuers = map[string]oauth2.Endpoint{
StgIssuer: {
AuthURL: "https://console-stg.cloud.vmware.com/csp/gateway/discovery",
TokenURL: "https://console-stg.cloud.vmware.com/csp/gateway/am/api/auth/authorize",
Expand All @@ -63,6 +69,16 @@ var (
TokenURL: "https://console.cloud.vmware.com/csp/gateway/am/api/auth/authorize",
AuthStyle: oauth2.AuthStyleInHeader,
},
StgIssuerTCSP: {
AuthURL: "https://console-stg.tanzu.broadcom.com/csp/gateway/discovery",
TokenURL: "https://console-stg.tanzu.broadcom.com/csp/gateway/am/api/auth/authorize",
AuthStyle: oauth2.AuthStyleInHeader,
},
ProdIssuerTCSP: {
AuthURL: "https://console.tanzu.broadcom.com/csp/gateway/discovery",
TokenURL: "https://console.tanzu.broadcom.com/csp/gateway/am/api/auth/authorize",
AuthStyle: oauth2.AuthStyleInHeader,
},
}
httpRestClient interfaces.HTTPClient
)
Expand Down Expand Up @@ -131,10 +147,11 @@ func GetAccessTokenFromAPIToken(apiToken, issuer string) (*Token, error) {

// GetIssuer returns the appropriate CSP issuer based on the environment.
func GetIssuer(staging bool) string {
cspMetadata := GetCSPMetadata()
if staging {
return StgIssuer
return cspMetadata.IssuerStaging
}
return ProdIssuer
return cspMetadata.IssuerProduction
}

// IsExpired checks for the token expiry and returns true if the token has expired else will return false
Expand Down Expand Up @@ -220,12 +237,16 @@ func GetToken(g *configapi.GlobalServerAuth) (*oauth2.Token, error) {
return nil, err
}
}

claims, err := ParseToken(&oauth2.Token{AccessToken: token.AccessToken})
if err != nil {
return nil, err
}
expiration := time.Now().Local().Add(time.Second * time.Duration(token.ExpiresIn))
g.Expiration = expiration
g.RefreshToken = token.RefreshToken
g.AccessToken = token.AccessToken
g.IDToken = token.IDToken
g.Permissions = claims.Permissions

tok := &oauth2.Token{
AccessToken: token.AccessToken,
Expand Down
12 changes: 7 additions & 5 deletions pkg/auth/csp/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,15 +256,17 @@ func TestGetToken_Expired(t *testing.T) {
Expiration: expireTime,
Type: APITokenType,
}

fakeHTTPClient := &fakes.FakeHTTPClient{}
responseBody := io.NopCloser(bytes.NewReader([]byte(`{
responseBodyFmt := `{
"id_token": "abc",
"token_type": "Test",
"expires_in": 86400,
"scope": "Test",
"access_token": "LetMeIn",
"refresh_token": "LetMeInAgain"}`)))
"access_token": "%s",
"refresh_token": "LetMeInAgain"}`

responseBodyStr := fmt.Sprintf(responseBodyFmt, accessToken)
responseBody := io.NopCloser(bytes.NewReader([]byte(responseBodyStr)))
fakeHTTPClient.DoReturns(&http.Response{
StatusCode: 200,
Body: responseBody,
Expand All @@ -274,6 +276,6 @@ func TestGetToken_Expired(t *testing.T) {
tok, err := GetToken(&serverAuth)
assert.Nil(err)
assert.NotNil(tok)
assert.Equal(tok.AccessToken, "LetMeIn")
assert.Equal(tok.AccessToken, accessToken)
assert.Equal(tok.RefreshToken, "LetMeInAgain")
}
15 changes: 3 additions & 12 deletions pkg/command/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,10 +754,7 @@ func updateTanzuContextMetadata(c *configtypes.Context, orgID, orgName, tanzuHub
// getCSPOrganizationName returns the CSP Org name using the orgID from the claims.
// It will return empty string if API fails
func getCSPOrganizationName(c *configtypes.Context, claims *csp.Claims) (string, error) {
issuer := csp.ProdIssuer
if staging {
issuer = csp.StgIssuer
}
issuer := csp.GetIssuer(staging)
if c.GlobalOpts == nil {
return "", errors.New("invalid context %q. Missing authorization fields")
}
Expand Down Expand Up @@ -796,10 +793,7 @@ func doCSPAuthentication(c *configtypes.Context) (*csp.Claims, error) {

func doCSPInteractiveLoginAndUpdateContext(c *configtypes.Context) (claims *csp.Claims, err error) {
logCSPOrgIDEnvVariableUsage()
issuer := csp.ProdIssuer
if staging {
issuer = csp.StgIssuer
}
issuer := csp.GetIssuer(staging)
cspOrgIDValue, cspOrgIDExists := os.LookupEnv(constants.CSPLoginOrgID)
var loginOptions []csp.LoginOption
if cspOrgIDExists && cspOrgIDValue != "" {
Expand Down Expand Up @@ -853,10 +847,7 @@ func logCSPOrgIDEnvVariableUsage() {
}

func doCSPAPITokenAuthAndUpdateContext(c *configtypes.Context, apiTokenValue string) (claims *csp.Claims, err error) {
issuer := csp.ProdIssuer
if staging {
issuer = csp.StgIssuer
}
issuer := csp.GetIssuer(staging)
token, err := csp.GetAccessTokenFromAPIToken(apiTokenValue, issuer)
if err != nil {
return nil, errors.Wrap(err, "failed to get the token from CSP")
Expand Down
Loading

0 comments on commit 2f71ea2

Please sign in to comment.