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
61 changes: 52 additions & 9 deletions awsenv/awsenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/gofrs/flock"
godotenv "github.com/joho/godotenv"
Expand All @@ -18,6 +19,22 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sts"
)

const defaultTimeout = 10 * time.Second

// Option configures the behavior of Ensure.
type Option func(*options)

type options struct {
timeout time.Duration
}

// WithTimeout sets the overall timeout for Ensure. Default is 10s.
func WithTimeout(d time.Duration) Option {
return func(o *options) {
o.timeout = d
}
}

// Ensure makes sure AWS credentials are available in the current process.
// It guarantees reading/writing `.aws.env` only at the project root (module dir from `go list`),
// validates credentials via STS, and refreshes them via `oidc2aws` if needed.
Expand All @@ -27,13 +44,35 @@ import (
// - QOR_AWS_REGION: AWS region to write if refreshing (default: ap-northeast-1)
// - QOR_OIDC2AWS_ALIAS: oidc2aws alias (default: qor5-test)
// - QOR_AWSENV_FORCE_REFRESH: if true-like, force refresh even if valid
func Ensure(ctx context.Context) error {
func Ensure(ctx context.Context, opts ...Option) error {
// Skip entirely in GitHub CI environments
if strings.EqualFold(strings.TrimSpace(os.Getenv("GITHUB_ACTIONS")), "true") {
return nil
}

projectRoot, err := detectProjectRoot()
o := &options{timeout: defaultTimeout}
for _, opt := range opts {
opt(o)
}

ctx, cancel := context.WithTimeout(ctx, o.timeout)
defer cancel()

result := make(chan error, 1)
go func() {
result <- doEnsure(ctx)
}()

select {
case err := <-result:
return err
case <-ctx.Done():
return errors.Errorf("awsenv.Ensure timed out after %s: your network connection may have issues or AWS/oidc2aws services are unreachable", o.timeout)
}
}

func doEnsure(ctx context.Context) error {
projectRoot, err := detectProjectRoot(ctx)
if err != nil {
return err
}
Expand All @@ -53,10 +92,14 @@ func Ensure(ctx context.Context) error {

lockPath := envFilePath + ".lock"
fileLock := flock.New(lockPath)
// Block until lock is acquired; minimal code path, acceptable for test bootstrap
if err := fileLock.Lock(); err != nil {
// Try to acquire lock with context so it respects the timeout
locked, err := fileLock.TryLockContext(ctx, 200*time.Millisecond)
if err != nil {
return errors.WithMessage(err, "failed to acquire awsenv lock")
}
if !locked {
return errors.New("failed to acquire awsenv lock: context cancelled")
}
defer func() {
_ = fileLock.Unlock()
}()
Expand All @@ -72,7 +115,7 @@ func Ensure(ctx context.Context) error {
alias := lo.Ternary(strings.TrimSpace(os.Getenv("QOR_OIDC2AWS_ALIAS")) == "", "qor5-test", os.Getenv("QOR_OIDC2AWS_ALIAS"))
region := lo.Ternary(strings.TrimSpace(os.Getenv("QOR_AWS_REGION")) == "", "ap-northeast-1", os.Getenv("QOR_AWS_REGION"))

content, err := runOidc2aws(alias)
content, err := runOidc2aws(ctx, alias)
if err != nil {
return err
}
Expand All @@ -93,15 +136,15 @@ func Ensure(ctx context.Context) error {

// detectProjectRoot gets the main module directory using the Go toolchain.
// Order: QOR_PROJECT_ROOT override -> `go list -m -f {{.Dir}}` -> error if empty/failed.
func detectProjectRoot() (string, error) {
func detectProjectRoot(ctx context.Context) (string, error) {
if root := strings.TrimSpace(os.Getenv("QOR_PROJECT_ROOT")); root != "" {
if stat, err := os.Stat(root); err == nil && stat.IsDir() {
return root, nil
}
return "", errors.New("QOR_PROJECT_ROOT is not a valid directory")
}

cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}")
cmd := exec.CommandContext(ctx, "go", "list", "-m", "-f", "{{.Dir}}")
cmd.Env = append(os.Environ(), "GOWORK=off")
var stdout bytes.Buffer
cmd.Stdout = &stdout
Expand Down Expand Up @@ -133,8 +176,8 @@ func validateAWSCredentials(ctx context.Context) error {
}

// runOidc2aws executes `oidc2aws -login -alias <alias> --env` and returns its stdout.
func runOidc2aws(alias string) (string, error) {
cmd := exec.Command("oidc2aws", "-login", "-alias", alias, "--env")
func runOidc2aws(ctx context.Context, alias string) (string, error) {
cmd := exec.CommandContext(ctx, "oidc2aws", "-login", "-alias", alias, "--env")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible command injection via shell script - medium severity
Your code spawns a subprocess via a shell script. User input could be abused to inject extra commands.

Show fix

Remediation: This issue can be mitigated or ignored if you verified or sanitized the user input used in the shell command.

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = os.Stderr
Expand Down
68 changes: 37 additions & 31 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,67 @@ module github.com/qor5/kx
go 1.24.0

require (
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/config v1.29.6
github.com/aws/aws-sdk-go-v2 v1.39.0
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/service/kms v1.38.3
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0
github.com/gofrs/flock v0.12.1
github.com/google/go-cmp v0.7.0
github.com/huandu/go-clone/generic v1.7.3
github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
github.com/samber/lo v1.50.0
github.com/stretchr/testify v1.10.0
github.com/theplant/appkit v0.0.0-20250528023215-3d0d299dc4c6
github.com/samber/lo v1.52.0
github.com/stretchr/testify v1.11.1
github.com/theplant/appkit v0.0.0-20251216060755-5c38a5ab9c14
github.com/tidwall/gjson v1.18.0
github.com/tidwall/sjson v1.2.5
golang.org/x/text v0.26.0
gorm.io/datatypes v1.2.5
golang.org/x/text v0.32.0
gorm.io/datatypes v1.2.7
)

require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.59 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.28 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.15 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.14 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-kit/kit v0.12.1-0.20220826005032-a7ba4fa4e289 // indirect
github.com/go-kit/log v0.2.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/huandu/go-clone v1.7.3 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jjeffery/errors v1.0.3 // indirect
github.com/jjeffery/kv v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/theplant/testingutils v0.0.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.33.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.39.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.77.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.6 // indirect
gorm.io/gorm v1.25.11 // indirect
gorm.io/driver/mysql v1.6.0 // indirect
gorm.io/driver/postgres v1.6.0 // indirect
gorm.io/gorm v1.31.1 // indirect
)
Loading
Loading