Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
uses: actions/checkout@v3

- name: Setup go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version-file: go.mod

- name: Build binary
run: |-
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golangci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
uses: actions/checkout@v3

- name: Setup go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: "1.20"
go-version-file: go.mod

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8
Expand Down
15 changes: 9 additions & 6 deletions cmd/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"time"

"github.com/atotto/clipboard"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/pkg/browser"
"github.com/spf13/cobra"
"jdk.sh/meta"
Expand Down Expand Up @@ -85,7 +85,7 @@ func Command() *cobra.Command { //nolint:cyclop,funlen
RunE: func(*cobra.Command, []string) error {
// Obtain credentials from either STDIN or a named AWS cli profile.
var (
creds *sts.Credentials
creds *aws.Credentials
err error
region string
)
Expand All @@ -104,11 +104,14 @@ func Command() *cobra.Command { //nolint:cyclop,funlen

// Set the preferred console region:
// - Use the value from --region if given.
// - Use the value from ~/.aws/config if given.
// - Use the value from $AWS_REGION if given.
// - Fall back to us-east-1.
if flags.region != "" {
switch {
case flags.region != "":
region = flags.region
} else if region == "" {
case os.Getenv("AWS_REGION") != "":
region = os.Getenv("AWS_REGION")
case region == "":
region = "us-east-1"
}

Expand All @@ -117,7 +120,7 @@ func Command() *cobra.Command { //nolint:cyclop,funlen
// If the named profile was configured with user credentials
// (opposed to a role), then the user must be federated before an
// AWS Console login url can be generated.
creds, err = credentials.FederateUser(creds, flags.federateName, federatePolicy, flags.duration, flags.userAgent)
creds, err = credentials.FederateUser(creds, region, flags.federateName, federatePolicy, flags.duration, flags.userAgent)
if err != nil {
return err
}
Expand Down
11 changes: 5 additions & 6 deletions console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,23 @@ import (
"strconv"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go-v2/aws"
)

// GenerateLoginURL takes the given sts.Credentials and generates a url.URL
// that can be used to login to the AWS Console.
// See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html.
func GenerateLoginURL(creds *sts.Credentials, duration time.Duration, location, userAgent string) (*url.URL, error) {
func GenerateLoginURL(creds *aws.Credentials, duration time.Duration, location, userAgent string) (*url.URL, error) {
// federationURL is the url used for AWS federation actions.
const federationURL = "https://signin.aws.amazon.com/federation"

// timeout is a hardcoded 15 second window for HTTP requests to complete.
const timeout = 15 * time.Second

sessionCreds := map[string]string{
"sessionId": aws.StringValue(creds.AccessKeyId),
"sessionKey": aws.StringValue(creds.SecretAccessKey),
"sessionToken": aws.StringValue(creds.SessionToken),
"sessionId": creds.AccessKeyID,
"sessionKey": creds.SecretAccessKey,
"sessionToken": creds.SessionToken,
}

// Encode our credentials into JSON.
Expand Down
88 changes: 39 additions & 49 deletions credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,38 @@
package credentials

import (
"context"
"encoding/json"
"errors"
"io"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/processcreds"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/aws-sdk-go-v2/service/sts/types"
)

// FromConfig retrieves credentials from the AWS cli config files, typically
// ~/.aws/credentials and ~/.aws/config. Credentials for the named profile are
// returned, or the default profile if no name is given. Additionally, the
// value of $AWS_PROFILE will be used if it is set.
func FromConfig(profile string) (*sts.Credentials, string, error) {
sess, err := session.NewSessionWithOptions(session.Options{
Profile: profile,
SharedConfigState: session.SharedConfigEnable,
})
func FromConfig(profile string) (*aws.Credentials, string, error) {
ctx := context.Background()

cfg, err := config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(profile))
if err != nil {
return nil, "", err
}

value, err := sess.Config.Credentials.Get()
creds, err := cfg.Credentials.Retrieve(ctx)
if err != nil {
return nil, "", err
}

return &sts.Credentials{
AccessKeyId: aws.String(value.AccessKeyID),
SecretAccessKey: aws.String(value.SecretAccessKey),
SessionToken: aws.String(value.SessionToken),
}, aws.StringValue(sess.Config.Region), nil
return &creds, cfg.Region, nil
}

// FromReader retrieves credentials from given io.Reader, typically os.Stdin.
Expand Down Expand Up @@ -72,36 +69,32 @@ func FromConfig(profile string) (*sts.Credentials, string, error) {
//
// See https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html#output.
// See https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html.
func FromReader(reader io.Reader) (*sts.Credentials, error) {
func FromReader(reader io.Reader) (*aws.Credentials, error) {
// Read the entire body, as it will be potentially parsed multiple times.
body, err := io.ReadAll(reader)
if err != nil {
return nil, err
}

type creds struct {
Credentials struct {
AccessKeyID string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
SessionToken string `json:"SessionToken"`
} `json:"Credentials"`
Credentials processcreds.CredentialProcessResponse `json:"Credentials"`
}

var result creds

if err := json.Unmarshal(body, &result); err == nil && result.Credentials.AccessKeyID != "" && result.Credentials.SecretAccessKey != "" {
// Credentials were unmarshalled into the entire struct.
return &sts.Credentials{
AccessKeyId: aws.String(result.Credentials.AccessKeyID),
SecretAccessKey: aws.String(result.Credentials.SecretAccessKey),
SessionToken: aws.String(result.Credentials.SessionToken),
return &aws.Credentials{
AccessKeyID: result.Credentials.AccessKeyID,
SecretAccessKey: result.Credentials.SecretAccessKey,
SessionToken: result.Credentials.SessionToken,
}, nil
} else if err := json.Unmarshal(body, &result.Credentials); err == nil && result.Credentials.AccessKeyID != "" && result.Credentials.SecretAccessKey != "" {
// Credentials were unmarshalled into part of the struct.
return &sts.Credentials{
AccessKeyId: aws.String(result.Credentials.AccessKeyID),
SecretAccessKey: aws.String(result.Credentials.SecretAccessKey),
SessionToken: aws.String(result.Credentials.SessionToken),
return &aws.Credentials{
AccessKeyID: result.Credentials.AccessKeyID,
SecretAccessKey: result.Credentials.SecretAccessKey,
SessionToken: result.Credentials.SessionToken,
}, nil
}

Expand All @@ -112,27 +105,24 @@ func FromReader(reader io.Reader) (*sts.Credentials, error) {
// FederateUser will federate the given user credentials by calling STS
// GetFederationToken. If the given credentials are not for a user (like
// credentials for a role) then they are returned unmodified.
func FederateUser(creds *sts.Credentials, name, policy string, duration time.Duration, userAgent string) (*sts.Credentials, error) {
func FederateUser(creds *aws.Credentials, region, name, policy string, duration time.Duration, _ string) (*aws.Credentials, error) {
// Only federate if user credentials were given.
if aws.StringValue(creds.SessionToken) != "" {
if creds.SessionToken != "" {
return creds, nil
}

// Create a new session given the static user credentials.
sess, err := session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(
aws.StringValue(creds.AccessKeyId),
aws.StringValue(creds.SecretAccessKey),
aws.StringValue(creds.SessionToken),
client := sts.NewFromConfig(aws.Config{
Credentials: credentials.NewStaticCredentialsProvider(
creds.AccessKeyID,
creds.SecretAccessKey,
creds.SessionToken,
),
Region: region,
})
if err != nil {
return nil, err
}

input := sts.GetFederationTokenInput{
Name: aws.String(name),
PolicyArns: []*sts.PolicyDescriptorType{{
PolicyArns: []types.PolicyDescriptorType{{
Arn: aws.String(policy),
}},
}
Expand All @@ -145,18 +135,18 @@ func FederateUser(creds *sts.Credentials, name, policy string, duration time.Dur
}

if duration != 0 {
input.DurationSeconds = aws.Int64(int64(duration.Seconds()))
input.DurationSeconds = aws.Int32(int32(duration.Seconds()))
}

// Configure client.
client := sts.New(sess)
client.Handlers.Build.PushBack(request.WithSetRequestHeaders(map[string]string{"User-Agent": userAgent}))

// Federate the user.
result, err := client.GetFederationToken(&input)
result, err := client.GetFederationToken(context.Background(), &input)
if err != nil {
return nil, err
}

return result.Credentials, nil
return &aws.Credentials{
AccessKeyID: aws.ToString(result.Credentials.AccessKeyId),
SecretAccessKey: aws.ToString(result.Credentials.SecretAccessKey),
SessionToken: aws.ToString(result.Credentials.SessionToken),
}, nil
}
19 changes: 15 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
module github.com/joshdk/aws-console

go 1.20
go 1.24.0

require (
github.com/atotto/clipboard v0.1.4
github.com/aws/aws-sdk-go v1.44.254
github.com/mattn/go-isatty v0.0.18
github.com/aws/aws-sdk-go-v2 v1.39.4
github.com/aws/aws-sdk-go-v2/config v1.31.15
github.com/aws/aws-sdk-go-v2/credentials v1.18.19
github.com/aws/aws-sdk-go-v2/service/sts v1.38.9
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sixel v0.0.5
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
Expand All @@ -14,8 +17,16 @@ require (
)

require (
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect
github.com/aws/smithy-go v1.23.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/soniakeys/quant v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.7.0 // indirect
Expand Down
Loading