Skip to content

Commit

Permalink
Merge pull request #169 from AlexVulaj/assume-debug
Browse files Browse the repository at this point in the history
add --debug-file flag for 'cloud assume' command
  • Loading branch information
openshift-merge-robot authored Aug 10, 2023
2 parents 59d49a8 + d1d9873 commit bf43a6a
Show file tree
Hide file tree
Showing 4 changed files with 431 additions and 49 deletions.
122 changes: 75 additions & 47 deletions cmd/ocm-backplane/cloud/assume.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,40 @@ import (
"encoding/json"
"fmt"
"io"
"os"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/spf13/cobra"

"github.com/openshift/backplane-cli/pkg/awsutil"
"github.com/openshift/backplane-cli/pkg/cli/config"
"github.com/openshift/backplane-cli/pkg/utils"
"github.com/spf13/cobra"
)

const (
DefaultRoleArn = "arn:aws:iam::922711891673:role/SRE-Support-Role"
DefaultInitialRoleArn = "arn:aws:iam::922711891673:role/SRE-Support-Role"
)

var assumeArgs struct {
roleArn string
output string
initialRoleArn string
output string
debugFile string
}

var StsClientWithProxy = awsutil.StsClientWithProxy
var AssumeRoleWithJWT = awsutil.AssumeRoleWithJWT
var NewStaticCredentialsProvider = credentials.NewStaticCredentialsProvider
var AssumeRoleSequence = awsutil.AssumeRoleSequence

var AssumeCmd = &cobra.Command{
Use: "assume [CLUSTERID|EXTERNAL_ID|CLUSTER_NAME|CLUSTER_NAME_SEARCH]",
Short: "Performs the assume role chaining necessary to generate temporary access to the customer's AWS account",
Long: `Performs the assume role chaining necessary to generate temporary access to the customer's AWS account
This command is the equivalent of running "aws sts assume-role-with-web-identity --role-arn [role-arn] --web-identity-token [ocm token] --role-session-name [email from OCM token]" behind the scenes,
This command is the equivalent of running "aws sts assume-role-with-web-identity --initial-role-arn [role-arn] --web-identity-token [ocm token] --role-session-name [email from OCM token]" behind the scenes,
where the ocm token used is the result of running "ocm token". Then, the command makes a call to the backplane API to get the necessary jump roles for the cluster's account. It then calls the
equivalent of "aws sts assume-role --role-arn [role-arn] --role-session-name [email from OCM token]" repeatedly for each role arn in the chain, using the previous role's credentials to assume the next
equivalent of "aws sts assume-role --initial-role-arn [role-arn] --role-session-name [email from OCM token]" repeatedly for each role arn in the chain, using the previous role's credentials to assume the next
role in the chain.
This command will output sts credentials for the target role in the given cluster in formatted JSON. If no "role-arn" is provided, a default role will be used.
Expand All @@ -41,15 +47,19 @@ This command will output sts credentials for the target role in the given cluste
backplane cloud assume e3b2fdc5-d9a7-435e-8870-312689cfb29c -oenv
With given role:
backplane cloud assume e3b2fdc5-d9a7-435e-8870-312689cfb29c --role-arn arn:aws:iam::1234567890:role/read-only -oenv`,
Args: cobra.ExactArgs(1),
backplane cloud assume e3b2fdc5-d9a7-435e-8870-312689cfb29c --initial-role-arn arn:aws:iam::1234567890:role/read-only -oenv
With a debug file:
backplane cloud assume e3b2fdc5-d9a7-435e-8870-312689cfb29c --debug-file test_arns`,
Args: cobra.MaximumNArgs(1),
RunE: runAssume,
}

func init() {
flags := AssumeCmd.Flags()
flags.StringVar(&assumeArgs.roleArn, "role-arn", DefaultRoleArn, "The arn of the role for which to start the role assume process.")
flags.StringVar(&assumeArgs.initialRoleArn, "initial-role-arn", DefaultInitialRoleArn, "The arn of the role for which to start the role assume process.")
flags.StringVarP(&assumeArgs.output, "output", "o", "env", "Format the output of the console response.")
flags.StringVar(&assumeArgs.debugFile, "debug-file", "", "A file containing the list of ARNs to assume in order, not including the initial role ARN. Providing this flag will bypass calls to the backplane API to retrieve the assume role chain. The file should be a plain text file with each ARN on a new line.")
}

type assumeChainResponse struct {
Expand All @@ -62,66 +72,84 @@ type namedRoleArn struct {
}

func runAssume(_ *cobra.Command, args []string) error {
if len(args) == 0 && assumeArgs.debugFile == "" {
return fmt.Errorf("must provide either cluster ID as an argument, or --debug-file as a flag")
}

ocmToken, err := utils.DefaultOCMInterface.GetOCMAccessToken()
if err != nil {
return fmt.Errorf("failed to retrieve OCM token: %w", err)
}

bpConfig, err := config.GetBackplaneConfiguration()
bpConfig, err := GetBackplaneConfiguration()
if err != nil {
return fmt.Errorf("error retrieving backplane configuration: %w", err)
}

initialClient, err := awsutil.StsClientWithProxy(bpConfig.ProxyURL)
initialClient, err := StsClientWithProxy(bpConfig.ProxyURL)
if err != nil {
return fmt.Errorf("failed to create sts client: %w", err)
}
seedCredentials, err := awsutil.AssumeRoleWithJWT(*ocmToken, assumeArgs.roleArn, initialClient)
seedCredentials, err := AssumeRoleWithJWT(*ocmToken, assumeArgs.initialRoleArn, initialClient)
if err != nil {
return fmt.Errorf("failed to assume role using JWT: %w", err)
}

clusterID, _, err := utils.DefaultOCMInterface.GetTargetCluster(args[0])
if err != nil {
return fmt.Errorf("failed to get target cluster: %w", err)
}

backplaneClient, err := utils.DefaultClientUtils.MakeRawBackplaneAPIClientWithAccessToken(bpConfig.URL, *ocmToken)
if err != nil {
return fmt.Errorf("failed to create backplane client with access token: %w", err)
}

response, err := backplaneClient.GetAssumeRoleSequence(context.TODO(), clusterID)
if err != nil {
return fmt.Errorf("failed to fetch arn sequence: %w", err)
}

bytes, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("failed to read backplane API response body: %w", err)
}

roleChainResponse := &assumeChainResponse{}
err = json.Unmarshal(bytes, roleChainResponse)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}

roleAssumeSequence := make([]string, 0, len(roleChainResponse.AssumptionSequence))
for _, namedRoleArn := range roleChainResponse.AssumptionSequence {
roleAssumeSequence = append(roleAssumeSequence, namedRoleArn.Arn)
}

email, err := utils.GetStringFieldFromJWT(*ocmToken, "email")
if err != nil {
return fmt.Errorf("unable to extract email from given token: %w", err)
}

seedClient := sts.NewFromConfig(aws.Config{
Region: "us-east-1",
Credentials: credentials.NewStaticCredentialsProvider(*seedCredentials.AccessKeyId, *seedCredentials.SecretAccessKey, *seedCredentials.SessionToken),
Credentials: NewStaticCredentialsProvider(*seedCredentials.AccessKeyId, *seedCredentials.SecretAccessKey, *seedCredentials.SessionToken),
})
targetCredentials, err := awsutil.AssumeRoleSequence(email, seedClient, roleAssumeSequence, bpConfig.ProxyURL, awsutil.DefaultSTSClientProviderFunc)

var roleAssumeSequence []string
if assumeArgs.debugFile == "" {
clusterID, _, err := utils.DefaultOCMInterface.GetTargetCluster(args[0])
if err != nil {
return fmt.Errorf("failed to get target cluster: %w", err)
}

backplaneClient, err := utils.DefaultClientUtils.MakeRawBackplaneAPIClientWithAccessToken(bpConfig.URL, *ocmToken)
if err != nil {
return fmt.Errorf("failed to create backplane client with access token: %w", err)
}

response, err := backplaneClient.GetAssumeRoleSequence(context.TODO(), clusterID)
if err != nil {
return fmt.Errorf("failed to fetch arn sequence: %w", err)
}
if response.StatusCode != 200 {
return fmt.Errorf("failed to fetch arn sequence: %v", response.Status)
}

bytes, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("failed to read backplane API response body: %w", err)
}

roleChainResponse := &assumeChainResponse{}
err = json.Unmarshal(bytes, roleChainResponse)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}

roleAssumeSequence = make([]string, 0, len(roleChainResponse.AssumptionSequence))
for _, namedRoleArn := range roleChainResponse.AssumptionSequence {
roleAssumeSequence = append(roleAssumeSequence, namedRoleArn.Arn)
}
} else {
arnBytes, err := os.ReadFile(assumeArgs.debugFile)
if err != nil {
return fmt.Errorf("failed to read file %v: %w", assumeArgs.debugFile, err)
}

roleAssumeSequence = append(roleAssumeSequence, strings.Split(string(arnBytes), "\n")...)
}

targetCredentials, err := AssumeRoleSequence(email, seedClient, roleAssumeSequence, bpConfig.ProxyURL, awsutil.DefaultSTSClientProviderFunc)
if err != nil {
return fmt.Errorf("failed to assume role sequence: %w", err)
}
Expand Down
Loading

0 comments on commit bf43a6a

Please sign in to comment.