Skip to content

Commit

Permalink
Add support to generate API token with tanzu api-token create (#820)
Browse files Browse the repository at this point in the history
* Add support to generate API token with `tanzu api-token create`

Signed-off-by: Anuj Chaudhari <[email protected]>
  • Loading branch information
anujc25 authored Oct 8, 2024
1 parent d3515fb commit e7a3e56
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/cli/commands/tanzu.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The Tanzu CLI

### SEE ALSO

* [tanzu api-token](tanzu_api-token.md) - Manage API Tokens for Tanzu Platform Self-managed
* [tanzu completion](tanzu_completion.md) - Output shell completion code
* [tanzu config](tanzu_config.md) - Configuration for the CLI
* [tanzu context](tanzu_context.md) - Configure and manage contexts for the Tanzu CLI
Expand Down
15 changes: 15 additions & 0 deletions docs/cli/commands/tanzu_api-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## tanzu api-token

Manage API Tokens for Tanzu Platform Self-managed

### Options

```
-h, --help help for api-token
```

### SEE ALSO

* [tanzu](tanzu.md) - The Tanzu CLI
* [tanzu api-token create](tanzu_api-token_create.md) - Create a new API Token for Tanzu Platform Self-managed

29 changes: 29 additions & 0 deletions docs/cli/commands/tanzu_api-token_create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## tanzu api-token create

Create a new API Token for Tanzu Platform Self-managed

```
tanzu api-token create [flags]
```

### Examples

```
# Create an API Token for the Tanzu Platform Self-managed
tanzu api-token create
# Note: The retrieved token can be used as the value of TANZU_API_TOKEN
# environment variable when running 'tanzu login' for non-interactive workflow.
```

### Options

```
-h, --help help for create
```

### SEE ALSO

* [tanzu api-token](tanzu_api-token.md) - Manage API Tokens for Tanzu Platform Self-managed

22 changes: 20 additions & 2 deletions docs/quickstart/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,18 +232,27 @@ Notes:

##### API Token

To authenticate with the Tanzu Platform Endpoint for non-interactive login, you must first generate an API token.

###### Generating an API Token

- For public endpoints, generate the API token via the [CSP UI](https://console.tanzu.broadcom.com).
- For Self-Managed use cases that rely on UAA, create the API token using the `tanzu api-token create` command.

###### Logging in with an API Token

Example command to log in using an API token:

```console
TANZU_API_TOKEN=<APIToken> tanzu login
TANZU_API_TOKEN=<APIToken> tanzu login [--endpoint <tanzu-platform-endpoint>]
```

Users can persist the environment variable in the CLI configuration file, which will be used for each CLI command
invocation:

```console
tanzu config set env.TANZU_API_TOKEN <api_token>
tanzu login
tanzu login [--endpoint <tanzu-platform-endpoint>]
```

### Creating and connecting to a new context
Expand Down Expand Up @@ -298,6 +307,15 @@ Notes:

##### API Token

To authenticate with the Tanzu Platform Endpoint for non-interactive login, you must first generate an API token.

###### Generating an API Token

- For public endpoints, generate the API token via the [CSP UI](https://console.tanzu.broadcom.com).
- For Self-Managed use cases that rely on UAA, create the API token using the `tanzu api-token create` command.

###### Logging in with an API Token

Example command for creating a tanzu context using an API token:

```console
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/gnostic v0.6.9
github.com/google/go-cmp v0.5.9
github.com/google/go-containerregistry v0.15.2
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.0
Expand Down Expand Up @@ -139,7 +140,6 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/certificate-transparency-go v1.1.6 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-github/v50 v50.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
Expand Down Expand Up @@ -196,6 +196,7 @@ require (
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
Expand Down
8 changes: 4 additions & 4 deletions pkg/auth/common/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import (
"github.com/vmware-tanzu/tanzu-plugin-runtime/config"
"github.com/vmware-tanzu/tanzu-plugin-runtime/config/types"
"github.com/vmware-tanzu/tanzu-plugin-runtime/log"

timeutils "github.com/vmware-tanzu/tanzu-cli/pkg/utils/time"
)

const (
extraIDToken = "id_token"
)

var currentTime = time.Now

const (
APITokenType = "api-token"
IDTokenType = "id-token"
Expand Down Expand Up @@ -99,7 +99,7 @@ func GetToken(g *types.GlobalServerAuth, tokenGetter func(refreshOrAPIToken, acc
g.RefreshToken = token.RefreshToken
g.AccessToken = token.AccessToken
g.IDToken = token.IDToken
expiration := currentTime().Local().Add(time.Duration(token.ExpiresIn) * time.Second)
expiration := timeutils.Now().Local().Add(time.Duration(token.ExpiresIn) * time.Second)
g.Expiration = expiration
g.Permissions = claims.Permissions

Expand Down Expand Up @@ -173,7 +173,7 @@ func ParseToken(tkn *oauth2.Token, idpType config.IdpType) (*Claims, error) {
func IsExpired(tokenExpiry time.Time) bool {
// refresh at half token life
two := 2
now := currentTime().Unix()
now := timeutils.Now().Unix()
halfDur := -time.Duration((tokenExpiry.Unix()-now)/int64(two)) * time.Second
return tokenExpiry.Add(halfDur).Unix() < now
}
7 changes: 4 additions & 3 deletions pkg/auth/common/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"

timeutils "github.com/vmware-tanzu/tanzu-cli/pkg/utils/time"
"github.com/vmware-tanzu/tanzu-plugin-runtime/config"
configtypes "github.com/vmware-tanzu/tanzu-plugin-runtime/config/types"
)
Expand Down Expand Up @@ -190,7 +191,7 @@ func TestGetToken_Valid_NotExpired(t *testing.T) {
func TestGetToken_Expired(t *testing.T) {
var theOneNow = time.Now()
// override currentTime to always returns same value
currentTime = func() time.Time {
timeutils.Now = func() time.Time {
return theOneNow
}

Expand All @@ -200,7 +201,7 @@ func TestGetToken_Expired(t *testing.T) {
`{"sub":"1234567890","username":"joe","context_name":"1516239022"}`,
)

expireTime := currentTime().Add(-time.Minute * 30)
expireTime := timeutils.Now().Add(-time.Minute * 30)

serverAuth := configtypes.GlobalServerAuth{
Issuer: "https://oidc.example.com",
Expand All @@ -213,7 +214,7 @@ func TestGetToken_Expired(t *testing.T) {
}

newRefreshToken := "LetMeInAgain"
newExpiryTime := currentTime().Local().Add(time.Minute * 30)
newExpiryTime := timeutils.Now().Local().Add(time.Minute * 30)
newExpiry := int64(30 * 60)

tokenGetter := createMockTokenGetter(newRefreshToken, newExpiry)
Expand Down
2 changes: 1 addition & 1 deletion pkg/auth/uaa/tanzu.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func GetAlternateClientID() string {
return clientID
}

func TanzuLogin(issuerURL string, opts ...common.LoginOption) (*common.Token, error) {
var TanzuLogin = func(issuerURL string, opts ...common.LoginOption) (*common.Token, error) {
issuerEndpoints := getIssuerEndpoints(issuerURL)

h := common.NewTanzuLoginHandler(issuerURL, issuerEndpoints.AuthURL, issuerEndpoints.TokenURL, tanzuCLIClientID, tanzuCLIClientSecret, defaultListenAddress, defaultCallbackPath, config.UAAIdpType, nil, nil, term.IsTerminal)
Expand Down
104 changes: 104 additions & 0 deletions pkg/command/apitoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2024 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package command

import (
"fmt"
"net/url"

"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/vmware-tanzu/tanzu-plugin-runtime/config"
"github.com/vmware-tanzu/tanzu-plugin-runtime/config/types"
"github.com/vmware-tanzu/tanzu-plugin-runtime/plugin"

commonauth "github.com/vmware-tanzu/tanzu-cli/pkg/auth/common"
"github.com/vmware-tanzu/tanzu-cli/pkg/auth/uaa"
"github.com/vmware-tanzu/tanzu-cli/pkg/cli"
"github.com/vmware-tanzu/tanzu-cli/pkg/constants"
)

func newAPITokenCmd() *cobra.Command {
apiTokenCmd := &cobra.Command{
Use: "api-token",
Short: "Manage API Tokens for Tanzu Platform Self-managed",
Aliases: []string{"apitoken"},
Annotations: map[string]string{
"group": string(plugin.SystemCmdGroup),
},
}

apiTokenCmd.SetUsageFunc(cli.SubCmdUsageFunc)
apiTokenCmd.AddCommand(
newAPITokenCreateCmd(),
)

return apiTokenCmd
}

func newAPITokenCreateCmd() *cobra.Command {
createCmd := &cobra.Command{
Use: "create",
Short: "Create a new API Token for Tanzu Platform Self-managed",
Aliases: []string{},
Example: `
# Create an API Token for the Tanzu Platform Self-managed
tanzu api-token create
# Note: The retrieved token can be used as the value of TANZU_API_TOKEN
# environment variable when running 'tanzu login' for non-interactive workflow.`,
RunE: createAPIToken,
ValidArgsFunction: noMoreCompletions,
}

return createCmd
}

func createAPIToken(cmd *cobra.Command, _ []string) (err error) {
c, err := config.GetActiveContext(types.ContextTypeTanzu)
if err != nil {
return errors.New("no active context found for Tanzu Platform. Please login to Tanzu Platform first to generate an API token")
}
if c == nil || c.GlobalOpts == nil || c.GlobalOpts.Auth.Issuer == "" {
return errors.New("invalid active context found for Tanzu Platform. Please login to Tanzu Platform first to generate an API token")
}
// Make sure it is of type tanzu with tanzuIdpType as `uaa` else return error
if idpType, exist := c.AdditionalMetadata[config.TanzuIdpTypeKey]; !exist || idpType != string(config.UAAIdpType) {
return errors.New("command no supported. Please refer to documentation on how to generate an API token for a public Tanzu Platform endpoint via https://console.tanzu.broadcom.com")
}

var token *commonauth.Token
// If user chooses to use a specific local listener port, use it
// Also specify the client ID to use for token generation
loginOptions := []commonauth.LoginOption{
commonauth.WithListenerPortFromEnv(constants.TanzuCLIOAuthLocalListenerPort),
commonauth.WithClientID(uaa.GetAlternateClientID()),
}

token, err = uaa.TanzuLogin(c.GlobalOpts.Auth.Issuer, loginOptions...)
if err != nil {
return errors.Wrap(err, "unable to login")
}

// Get tanzu platform endpoint as best effort from the existing context
tpEndpoint := "<tanzu-platform-endpoint>"
if hubEndpoint, exist := c.AdditionalMetadata[config.TanzuHubEndpointKey]; exist && hubEndpoint != nil {
u, err := url.Parse(hubEndpoint.(string))
if err == nil {
tpEndpoint = fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}
}

cyanBold := color.New(color.FgCyan).Add(color.Bold)
bold := color.New(color.Bold)

fmt.Fprint(cmd.OutOrStdout(), bold.Sprint("==\n\n"))
fmt.Fprintf(cmd.OutOrStdout(), "%s Your generated API token is: %s\n\n", bold.Sprint("API Token Generation Successful!"), cyanBold.Sprint(token.RefreshToken))
fmt.Fprintf(cmd.OutOrStdout(), "For Tanzu CLI use in non-interactive settings, set the environment variable %s before authenticating with the command %s\n\n", cyanBold.Sprintf("TANZU_API_TOKEN=%s", token.RefreshToken), cyanBold.Sprintf("tanzu login --endpoint %s", tpEndpoint))
fmt.Fprint(cmd.OutOrStdout(), "Please copy and save your token securely. Note that you will need to regenerate a new token before expiration time and login again to continue using the CLI.\n")

return nil
}
Loading

0 comments on commit e7a3e56

Please sign in to comment.