package rootaws import ( "bytes" "context" "log" "os" "path/filepath" "strings" "testing" "golang.org/x/sys/unix" ) func TestRootCredentialsRejectUnsafeFiles(t *testing.T) { for _, kind := range []string{"symlink", "dangling", "hardlink", "fifo", "directory", "service-owner", "group-write", "other-write", "process"} { t.Run(kind, func(t *testing.T) { isolatedAWSEnv(t) dir := t.TempDir() path := filepath.Join(dir, "credentials") target := filepath.Join(dir, "target") body := []byte("[default]\naws_access_key_id=ACCESS\naws_secret_access_key=SECRET\n") if err := os.WriteFile(target, body, 0600); err != nil { t.Fatal(err) } var err error switch kind { case "symlink": err = os.Symlink(target, path) case "dangling": err = os.Symlink(filepath.Join(dir, "missing"), path) case "hardlink": err = os.Link(target, path) case "fifo": err = unix.Mkfifo(path, 0600) case "directory": err = os.Mkdir(path, 0700) default: if os.Geteuid() != 0 && kind != "service-owner" { t.Skip("requires root-owned fixture to isolate mode and content checks") } if kind == "process" { body = append(body, []byte("credential_process=bad\n")...) } err = os.WriteFile(path, body, 0600) if err == nil { switch kind { case "service-owner": if os.Geteuid() == 0 { err = os.Chown(path, 65534, 65534) } case "group-write": err = os.Chmod(path, 0660) case "other-write": err = os.Chmod(path, 0602) } } } if err != nil { t.Fatal(err) } var logs bytes.Buffer old := log.Writer() log.SetOutput(&logs) defer log.SetOutput(old) cfg, err := loadRootConfig(context.Background(), path) if err != nil { t.Fatalf("unused unsafe credentials made config loading fatal: %v", err) } if _, err := cfg.Credentials.Retrieve(context.Background()); err == nil { t.Fatal("unsafe file accepted") } if !strings.Contains(logs.String(), "ALERT:") { t.Fatal("unsafe file refusal did not alert") } }) } } func TestRootCredentialsEnvironmentKeysIgnoreServiceOwnedFile(t *testing.T) { isolatedAWSEnv(t) t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS") t.Setenv("AWS_SECRET_ACCESS_KEY", "ENV_SECRET") path := filepath.Join(t.TempDir(), "credentials") if err := os.WriteFile(path, []byte("[default]\naws_access_key_id=FILE\naws_secret_access_key=SECRET\n"), 0600); err != nil { t.Fatal(err) } if os.Geteuid() == 0 { if err := os.Chown(path, 65534, 65534); err != nil { t.Fatal(err) } } // Without environment keys the same file is refused, so the file is unsafe. t.Setenv("AWS_SECRET_ACCESS_KEY", "") unsafeConfig, err := loadRootConfig(context.Background(), path) if err != nil { t.Fatal(err) } if _, err := unsafeConfig.Credentials.Retrieve(context.Background()); err == nil { t.Fatal("service-owned fixture was not unsafe") } t.Setenv("AWS_SECRET_ACCESS_KEY", "ENV_SECRET") cfg, err := loadRootConfig(context.Background(), path) if err != nil { t.Fatalf("service-owned unused file made config fatal: %v", err) } got, err := cfg.Credentials.Retrieve(context.Background()) if err != nil || got.AccessKeyID != "ENV_ACCESS" || got.SecretAccessKey != "ENV_SECRET" { t.Fatalf("environment credentials failed: %v", err) } } func TestRootCredentialsStaticProvider(t *testing.T) { if os.Geteuid() != 0 { t.Skip("requires root-owned credentials fixture") } isolatedAWSEnv(t) path := filepath.Join(t.TempDir(), "credentials") if err := os.WriteFile(path, []byte("[default]\naws_access_key_id=STATIC\naws_secret_access_key=SECRET\naws_session_token=TOKEN\n"), 0640); err != nil { t.Fatal(err) } fileCreds, err := offcloudCredentials(path) if err != nil || fileCreds.AccessKeyID != "STATIC" || fileCreds.SecretAccessKey != "SECRET" || fileCreds.SessionToken != "TOKEN" { t.Fatalf("off-cloud file credentials failed: %v", err) } cfg, err := loadRootConfig(context.Background(), path) if err != nil { t.Fatal(err) } // The provider holds the parsed snapshot and never asks the SDK to reread it. if err := os.WriteFile(path, []byte("[default]\ncredential_process=bad\n"), 0640); err != nil { t.Fatal(err) } got, err := cfg.Credentials.Retrieve(context.Background()) if err != nil || got.AccessKeyID != "STATIC" || got.SessionToken != "TOKEN" { t.Fatalf("static snapshot failed: %v", err) } }