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
5 changes: 5 additions & 0 deletions .changeset/cli-access-key-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tigrisdata/cli": minor
---

`tigris access-keys create --default-permissions disable` creates a key without the default permissions — it can do only what its roles and policies explicitly allow — and `tigris access-keys set <id> --default-permissions enable|disable` changes the setting on an existing key, leaving its bucket roles as they are. This is the console's "Enable default permissions" toggle; JSON output carries it as the SDK's `scope` (`standard` or `no_default_allow`). `access-keys get` and `access-keys list` show the setting for every key.
5 changes: 5 additions & 0 deletions .changeset/iam-access-key-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tigrisdata/iam": minor
---

Access keys gain a scope. `createAccessKey(name, { scope })` sets it at creation and the new `setAccessKeyScope(id, scope)` changes it on an existing key, leaving its bucket roles as they are. `standard` (the default) grants the implicit default-allowed operations such as creating and listing buckets on top of what the key's policies and bucket roles grant; `no_default_allow` limits the key to what they explicitly allow. `getAccessKey` and `listAccessKeys` report it as `scope`, and `ACCESS_KEY_SCOPES` lists the valid values.
25 changes: 23 additions & 2 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1403,8 +1403,9 @@ Create, list, inspect, delete, and assign roles to access keys. Access keys are
| `tigris access-keys list` (l) | List all access keys in the current organization |
| `tigris access-keys create` (c) | Create a new access key, optionally scoped to buckets, and export its credentials to a dotenv file or your shell. Returns the key ID and secret (shown only once) |
| `tigris access-keys delete` (d) | Permanently delete one or more access keys by ID. This revokes all access immediately |
| `tigris access-keys get` (g) | Show details for an access key including its name, creation date, and assigned bucket roles |
| `tigris access-keys get` (g) | Show details for an access key including its name, creation date, default permissions, and assigned bucket roles |
| `tigris access-keys assign` (a) | Assign per-bucket roles to an access key. Pair each --bucket with a --role (Editor, ReadWrite, or ReadOnly), or use --admin for org-wide access |
| `tigris access-keys set` (s) | Update settings on an existing access key — currently its default permissions, the console's "Enable default permissions" toggle. Bucket roles are left as they are |
| `tigris access-keys rotate` (r) | Rotate an access key's secret. The current secret is immediately invalidated and a new one is returned (shown only once) |
| `tigris access-keys attach-policy` (ap) | Attach an IAM policy to an access key. If no policy ARN is provided, shows interactive selection of available policies |
| `tigris access-keys detach-policy` (dp) | Detach an IAM policy from an access key. If no policy ARN is provided, shows interactive selection of attached policies |
Expand Down Expand Up @@ -1442,6 +1443,7 @@ tigris access-keys create <name> [flags]
| `-b, --bucket` | Bucket name (can specify multiple, comma-separated). Each bucket is paired positionally with a --role value |
| `-r, --role` | Role to assign (can specify multiple, comma-separated). Each role pairs with the corresponding --bucket value |
| `--admin` | Grant admin access to all buckets in the organization |
| `--default-permissions` | Whether the key also gets the default-allowed operations (such as creating and listing buckets) on top of its roles and policies — the console's "Enable default permissions" toggle. Enabled unless disabled |
| `--env` | Write the credentials to a dotenv file, merging into it when it already exists. Defaults to ./.env. The secret is then not printed |
| `--export` | Print the credentials as shell "export" lines and nothing else on stdout, for eval "$(tigris access-keys create <name> --export)" |
| `--for` | Which SDK the credentials are for. --env and --export then use that SDK's variable names. tigris writes TIGRIS_STORAGE_*; aws writes AWS_* plus Tigris endpoints and region (default: tigris) |
Expand All @@ -1450,6 +1452,7 @@ tigris access-keys create <name> [flags]
```bash
tigris access-keys create my-ci-key
tigris access-keys create my-app --bucket my-app-bucket --role Editor --env
tigris access-keys create ci --bucket my-app-bucket --role ReadOnly --default-permissions disable
eval "$(tigris access-keys create dev --bucket my-app-bucket --role Editor --export)"
```

Expand All @@ -1474,7 +1477,7 @@ tigris access-keys delete tid_AaBb,tid_CcDd --yes

#### `tigris access-keys get` (g)

Show details for an access key including its name, creation date, and assigned bucket roles
Show details for an access key including its name, creation date, default permissions, and assigned bucket roles

```
tigris access-keys get <id>
Expand Down Expand Up @@ -1508,6 +1511,24 @@ tigris access-keys assign tid_AaBb --admin
tigris access-keys assign tid_AaBb --revoke-roles
```

#### `tigris access-keys set` (s)

Update settings on an existing access key — currently its default permissions, the console's "Enable default permissions" toggle. Bucket roles are left as they are

```
tigris access-keys set <id> [flags]
```

| Flag | Description |
|------|-------------|
| `--default-permissions` | Whether the key also gets the default-allowed operations (such as creating and listing buckets) on top of its roles and policies — the console's "Enable default permissions" toggle. Enabled unless disabled |

**Examples:**
```bash
tigris access-keys set tid_AaBb --default-permissions disable
tigris access-keys set tid_AaBb --default-permissions enable
```

#### `tigris access-keys rotate` (r)

Rotate an access key's secret. The current secret is immediately invalidated and a new one is returned (shown only once)
Expand Down
14 changes: 13 additions & 1 deletion packages/cli/src/lib/access-keys/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
printSuccess,
} from '@utils/messages.js';
import { getFormat, getOption } from '@utils/options.js';

import {
describeDefaultPermissions,
getDefaultPermissions,
} from './default-permissions.js';
import {
buildRoleAssignments,
getRoleRequest,
Expand Down Expand Up @@ -65,6 +68,8 @@ export default async function create(options: Record<string, unknown>) {
);
}

const scope = getDefaultPermissions(context, options);

const assignments = requestsRoles(request)
? buildRoleAssignments(context, request, ' (or use --admin)')
: undefined;
Expand All @@ -90,6 +95,7 @@ export default async function create(options: Record<string, unknown>) {
const { data, error } = await createAccessKey(name, {
config,
bucketsRole: assignments,
...(scope ? { scope } : {}),
});

if (error) {
Expand Down Expand Up @@ -151,6 +157,7 @@ export default async function create(options: Record<string, unknown>) {
secret: data.secret,
};
if (assignments) output.roles = assignments;
if (data.scope) output.scope = data.scope;
if (writtenPath) output.env = writtenPath;
if (gitignored !== undefined) output.gitignored = gitignored;
if (envError) output.envError = envError.message;
Expand All @@ -172,6 +179,11 @@ export default async function create(options: Record<string, unknown>) {
` Roles: ${assignments.map((a) => `${a.role} on ${a.bucket}`).join(', ')}`
);
}
if (data.scope) {
console.log(
` Default permissions: ${describeDefaultPermissions(data.scope)}`
);
}
if (!writtenPath) {
console.log('');
console.log(
Expand Down
41 changes: 41 additions & 0 deletions packages/cli/src/lib/access-keys/default-permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { AccessKeyScope } from '@tigrisdata/iam';
import { failWithError } from '@utils/exit.js';
import type { MessageContext } from '@utils/messages.js';
import { getOption } from '@utils/options.js';

/**
* The console's "Enable default permissions" toggle, as the SDK's scope:
* enabled is the `standard` scope, disabled is `no_default_allow`.
*/
const SCOPE_BY_SETTING = {
enable: 'standard',
disable: 'no_default_allow',
} as const satisfies Record<string, AccessKeyScope>;

type Setting = keyof typeof SCOPE_BY_SETTING;

const SETTINGS = Object.keys(SCOPE_BY_SETTING) as Setting[];

/** `--default-permissions` as the scope it stands for; undefined when absent. */
export function getDefaultPermissions(
context: MessageContext,
options: Record<string, unknown>
): AccessKeyScope | undefined {
const setting = getOption<string>(options, [
'default-permissions',
'defaultPermissions',
]);
if (setting === undefined) return undefined;
if (!SETTINGS.includes(setting as Setting)) {
failWithError(
context,
`Invalid --default-permissions "${setting}". Valid values are: ${SETTINGS.join(', ')}`
);
}
return SCOPE_BY_SETTING[setting as Setting];
}

/** How the setting reads back: enabled for `standard`, disabled otherwise. */
export function describeDefaultPermissions(scope: AccessKeyScope): string {
return scope === 'standard' ? 'enabled' : 'disabled';
}
7 changes: 7 additions & 0 deletions packages/cli/src/lib/access-keys/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { failWithError } from '@utils/exit.js';
import { msg, printStart, printSuccess } from '@utils/messages.js';
import { getFormat, getOption } from '@utils/options.js';

import { describeDefaultPermissions } from './default-permissions.js';

const context = msg('access-keys', 'get');

export default async function get(options: Record<string, unknown>) {
Expand Down Expand Up @@ -33,6 +35,11 @@ export default async function get(options: Record<string, unknown>) {
console.log(` Status: ${data.status}`);
console.log(` Created: ${data.createdAt}`);
console.log(` Organization: ${data.organizationId}`);
if (data.scope) {
console.log(
` Default permissions: ${describeDefaultPermissions(data.scope)}`
);
}

if (data.roles && data.roles.length > 0) {
console.log(` Roles:`);
Expand Down
15 changes: 14 additions & 1 deletion packages/cli/src/lib/access-keys/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from '@utils/messages.js';
import { getFormat, getPaginationOptions } from '@utils/options.js';

import { describeDefaultPermissions } from './default-permissions.js';

const context = msg('access-keys', 'list');

export default async function list(options: Record<string, unknown>) {
Expand Down Expand Up @@ -40,19 +42,30 @@ export default async function list(options: Record<string, unknown>) {
name: key.name,
id: key.id,
status: key.status,
scope: key.scope,
created: key.createdAt,
}));

// JSON and XML carry the SDK's `scope`; the table reads like the console.
const machineReadable = format === 'json' || format === 'xml';
const rows = machineReadable
? keys
: keys.map((key) => ({
...key,
scope: key.scope && describeDefaultPermissions(key.scope),
}));

const columns = [
{ key: 'name', header: 'Name' },
{ key: 'id', header: 'ID' },
{ key: 'status', header: 'Status' },
{ key: 'scope', header: 'Default permissions' },
{ key: 'created', header: 'Created' },
];

const nextToken = data.paginationToken || undefined;

const output = formatPaginatedOutput(keys, format!, 'keys', 'key', columns, {
const output = formatPaginatedOutput(rows, format!, 'keys', 'key', columns, {
paginationToken: nextToken,
});

Expand Down
44 changes: 44 additions & 0 deletions packages/cli/src/lib/access-keys/set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getIAMConfig } from '@auth/iam.js';
import { setAccessKeyScope } from '@tigrisdata/iam';
import { failWithError } from '@utils/exit.js';
import { msg, printStart, printSuccess } from '@utils/messages.js';
import { getFormat, getOption } from '@utils/options.js';

import {
describeDefaultPermissions,
getDefaultPermissions,
} from './default-permissions.js';

const context = msg('access-keys', 'set');

export default async function set(options: Record<string, unknown>) {
printStart(context);

const format = getFormat(options);
const id = getOption<string>(options, ['id']);
const scope = getDefaultPermissions(context, options);

if (!id) {
failWithError(context, 'Access key ID is required');
}
if (!scope) {
failWithError(
context,
'Nothing to change: pass --default-permissions enable or disable'
);
}

const config = await getIAMConfig(context);

const { error } = await setAccessKeyScope(id, scope, { config });

if (error) {
failWithError(context, error);
}

if (format === 'json') {
console.log(JSON.stringify({ action: 'updated', id, scope }));
}

printSuccess(context, { id, setting: describeDefaultPermissions(scope) });
}
30 changes: 28 additions & 2 deletions packages/cli/src/specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ definitions:
name: admin
description: Grant admin access to all buckets in the organization
type: flag
key_default_permissions_argument: &key_default_permissions_argument
name: default-permissions
description: Whether the key also gets the default-allowed operations (such as creating and listing buckets) on top of its roles and policies — the console's "Enable default permissions" toggle. Enabled unless disabled
helpText: Default permissions beyond the key's roles and policies (enable or disable)
options: [enable, disable]

global_arguments: &global_arguments
- name: format
Expand Down Expand Up @@ -2269,6 +2274,7 @@ commands:
examples:
- "tigris access-keys create my-ci-key"
- "tigris access-keys create my-app --bucket my-app-bucket --role Editor --env"
- "tigris access-keys create ci --bucket my-app-bucket --role ReadOnly --default-permissions disable"
- 'eval "$(tigris access-keys create dev --bucket my-app-bucket --role Editor --export)"'
messages:
onStart: 'Creating access key...'
Expand All @@ -2287,6 +2293,7 @@ commands:
- *key_bucket_argument
- *key_role_argument
- *key_admin_argument
- *key_default_permissions_argument
- name: env
description: Write the credentials to a dotenv file, merging into it when it already exists. Defaults to ./.env. The secret is then not printed
helpText: Write the credentials to a dotenv file (default ./.env)
Expand Down Expand Up @@ -2326,8 +2333,8 @@ commands:
type: flag
description: Skip confirmation prompts (alias for --yes)
- name: get
description: Show details for an access key including its name, creation date, and assigned bucket roles
helpText: Show an access key's details and bucket roles
description: Show details for an access key including its name, creation date, default permissions, and assigned bucket roles
helpText: Show an access key's details, permissions, and bucket roles
alias: g
examples:
- "tigris access-keys get tid_AaBbCcDdEeFf"
Expand Down Expand Up @@ -2371,6 +2378,25 @@ commands:
- name: revoke-roles
description: Revoke all bucket roles from the access key
type: flag
- name: set
description: Update settings on an existing access key — currently its default permissions, the console's "Enable default permissions" toggle. Bucket roles are left as they are
helpText: Update settings on an existing access key
alias: s
examples:
- "tigris access-keys set tid_AaBb --default-permissions disable"
- "tigris access-keys set tid_AaBb --default-permissions enable"
messages:
onStart: 'Updating access key...'
onSuccess: "Default permissions {{setting}} for access key '{{id}}'"
onFailure: 'Failed to update access key'
arguments:
- name: id
description: Access key ID
type: positional
required: true
examples:
- tid_AaBbCcDdEeFf
- *key_default_permissions_argument
- name: rotate
description: Rotate an access key's secret. The current secret is immediately invalidated and a new one is returned (shown only once)
helpText: Rotate an access key's secret
Expand Down
Loading