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
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ func Init(name string, awsCredentials bool, sessionDuration int) (*App, error) {
if awsCredentials {
cfg, err := config.LoadDefaultConfig(context.Background())
if err != nil {
return nil, err
return nil, auth.FriendlyAWSConfigError(err)
}
app.Session = cfg
app.AWS = apppackaws.New(cfg)
Expand Down
35 changes: 35 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/url"
"strings"

"github.com/apppackio/apppack/state"
"github.com/aws/aws-sdk-go-v2/aws"
Expand All @@ -13,6 +14,38 @@ import (
"github.com/sirupsen/logrus"
)

// ignoreSharedConfigFiles disables loading of the local AWS shared config and
// credentials files (~/.aws/config, ~/.aws/credentials). AppPack's OAuth flow
// injects its own credentials, so those files are never needed here. Without
// this, a partially-configured [default] profile on the user's machine makes
// every non-`auth` command fail with an opaque SDK error.
func ignoreSharedConfigFiles() config.LoadOptionsFunc {
return func(o *config.LoadOptions) error {
o.SharedConfigFiles = []string{}
o.SharedCredentialsFiles = []string{}
return nil
}
}

// FriendlyAWSConfigError adds a hint to errors returned when loading the local
// AWS configuration (the `--aws-credentials` flow, which intentionally reads
// the user's ~/.aws files). The raw SDK "partial credentials" error is opaque;
// this points the user at the real, machine-local cause.
func FriendlyAWSConfigError(err error) error {
if err == nil {
return nil
}
if strings.Contains(err.Error(), "partial credentials") {
return fmt.Errorf(
"%w: your local AWS credentials appear to be incomplete — "+
"check for a partial [default] profile in ~/.aws/credentials or ~/.aws/config, "+
"or stray AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY environment variables",
err,
)
}
return err
}

const (
deviceCodeURL = "https://auth.apppack.io/oauth/device/code"
oauthTokenURL = "https://auth.apppack.io/oauth/token" // #nosec G101 -- URL path, not a credential
Expand Down Expand Up @@ -53,6 +86,7 @@ func AppAWSSession(appName string, sessionDuration int) (aws.Config, *AppRole, e
*creds.SessionToken,
)),
config.WithRegion(appRole.Region),
ignoreSharedConfigFiles(),
)
if err != nil {
return aws.Config{}, nil, err
Expand Down Expand Up @@ -90,6 +124,7 @@ func AdminAWSSession(idOrAlias string, sessionDuration int, region string) (aws.
*creds.SessionToken,
)),
config.WithRegion(region),
ignoreSharedConfigFiles(),
)
if err != nil {
return aws.Config{}, nil, err
Expand Down
37 changes: 37 additions & 0 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package auth

import (
"errors"
"strings"
"testing"
)

func TestFriendlyAWSConfigError(t *testing.T) {
t.Run("nil error stays nil", func(t *testing.T) {
if err := FriendlyAWSConfigError(nil); err != nil {
t.Fatalf("expected nil, got %v", err)
}
})

t.Run("partial credentials error gets a hint", func(t *testing.T) {
raw := errors.New("error fetching config from profile, default, Error using profile: \n 2, partial credentials found for profile default")
got := FriendlyAWSConfigError(raw)
if got == nil {
t.Fatal("expected wrapped error, got nil")
}
if !strings.Contains(got.Error(), "local AWS credentials appear to be incomplete") {
t.Errorf("expected friendly hint, got: %v", got)
}
if !errors.Is(got, raw) {
t.Error("expected wrapped error to preserve the original via errors.Is")
}
})

t.Run("unrelated error is passed through unchanged", func(t *testing.T) {
raw := errors.New("some other failure")
got := FriendlyAWSConfigError(raw)
if !errors.Is(got, raw) || got.Error() != raw.Error() {
t.Errorf("expected passthrough, got: %v", got)
}
})
}
1 change: 1 addition & 0 deletions auth/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ func (t *Tokens) GetCredentials(role Role, duration int) (*types.Credentials, er

cfg, err := config.LoadDefaultConfig(context.Background(),
config.WithRegion("us-east-1"), // STS is a global service, use us-east-1 as default
ignoreSharedConfigFiles(),
)
if err != nil {
return nil, err
Expand Down
5 changes: 3 additions & 2 deletions cmd/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,13 @@ func adminSession(sessionDuration int) (aws.Config, error) {
if UseAWSCredentials {
ctx := context.Background()
if region != "" {
return config.LoadDefaultConfig(ctx, config.WithRegion(region))
cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
return cfg, auth.FriendlyAWSConfigError(err)
}

cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return aws.Config{}, err
return aws.Config{}, auth.FriendlyAWSConfigError(err)
}

if cfg.Region == "" {
Expand Down
Loading