Siddhant Khare 8d2c8d86c2
[CLI] --decode flag to decode IDP token to JSON (#18750)
* [CLI] `--decode` flag to decode IDP token to JSON

* Marhsal into a map into a proper JSON output
2023-09-20 17:20:59 +02:00

146 lines
4.1 KiB
Go

// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.
package cmd
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
connect "github.com/bufbuild/connect-go"
"github.com/gitpod-io/gitpod/common-go/util"
"github.com/gitpod-io/gitpod/components/public-api/go/client"
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
"github.com/gitpod-io/gitpod/gitpod-cli/pkg/gitpod"
supervisor "github.com/gitpod-io/gitpod/supervisor/api"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var idpTokenOpts struct {
Audience []string
Decode bool
}
var idpTokenCmd = &cobra.Command{
Use: "token",
Short: "Requests an ID token for this workspace",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
ctx, cancel := context.WithTimeout(cmd.Context(), 5*time.Second)
defer cancel()
tkn, err := idpToken(ctx, idpTokenOpts.Audience)
// If the user wants to decode the token, then do so.
if idpTokenOpts.Decode {
// Split the token into its three parts.
parts := strings.Split(tkn, ".")
if len(parts) != 3 {
xerrors.Errorf("JWT token is not valid")
}
// Decode the header.
header, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
xerrors.Errorf("Failed to decode header: ", err)
}
// Decode the payload.
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
xerrors.Errorf("Failed to decode payload: ", err)
}
// Unmarshal the header and payload into JSON.
var headerJSON map[string]interface{}
var payloadJSON map[string]interface{}
// This function unmarshals the header and payload of a JWT.
// The header is unmarshalled into a HeaderJSON struct, and the payload is unmarshalled into a PayloadJSON struct.
if err := json.Unmarshal(header, &headerJSON); err != nil {
xerrors.Errorf("Failed to unmarshal header: ", err)
}
if err := json.Unmarshal(payload, &payloadJSON); err != nil {
xerrors.Errorf("Failed to unmarshal payload: ", err)
}
output := map[string]interface{}{
"Header": headerJSON,
"Payload": payloadJSON,
}
// The header and payload are then marshalled into a map and printed to the screen.
outputPretty, err := json.MarshalIndent(output, "", " ")
if err != nil {
xerrors.Errorf("Failed to marshal output: ", err)
}
fmt.Printf("%s\n", outputPretty)
return nil
}
if err != nil {
return err
}
fmt.Println(tkn)
return nil
},
}
func idpToken(ctx context.Context, audience []string) (idToken string, err error) {
wsInfo, err := gitpod.GetWSInfo(ctx)
if err != nil {
return "", err
}
supervisorConn, err := grpc.Dial(util.GetSupervisorAddress(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return "", xerrors.Errorf("failed connecting to supervisor: %w", err)
}
defer supervisorConn.Close()
clientToken, err := supervisor.NewTokenServiceClient(supervisorConn).GetToken(ctx, &supervisor.GetTokenRequest{
Host: wsInfo.GitpodApi.Host,
Kind: "gitpod",
Scope: []string{
"function:getWorkspace",
},
})
if err != nil {
return "", xerrors.Errorf("failed getting token from supervisor: %w", err)
}
c, err := client.New(client.WithCredentials(clientToken.Token), client.WithURL("https://api."+wsInfo.GitpodApi.Host))
if err != nil {
return "", err
}
tkn, err := c.IdentityProvider.GetIDToken(ctx, &connect.Request[v1.GetIDTokenRequest]{
Msg: &v1.GetIDTokenRequest{
Audience: audience,
WorkspaceId: wsInfo.WorkspaceId,
},
})
if err != nil {
return "", err
}
return tkn.Msg.Token, nil
}
func init() {
idpCmd.AddCommand(idpTokenCmd)
idpTokenCmd.Flags().StringArrayVar(&idpTokenOpts.Audience, "audience", nil, "audience of the ID token")
_ = idpTokenCmd.MarkFlagRequired("audience")
idpTokenCmd.Flags().BoolVar(&idpTokenOpts.Decode, "decode", false, "decode token to JSON")
}