From 5296d0fff64c482f71e582d18d9aecd2be06a3ec Mon Sep 17 00:00:00 2001 From: Kane Petra Date: Thu, 19 Oct 2023 13:23:03 +0200 Subject: [PATCH 1/4] feat(tsh): support username generation --- src/tsh.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/tsh.ts b/src/tsh.ts index 42b02b1a5c79..95e3eda46b25 100644 --- a/src/tsh.ts +++ b/src/tsh.ts @@ -93,12 +93,33 @@ const completionSpec: Fig.Spec = { name: "user@hostname", description: "Address of remote machine to log into", generators: { - script: "tsh ls --format=json", - postProcess: (out) => { + trigger: (curr, prev) => { + return curr.lastIndexOf("@") !== prev.lastIndexOf("@"); + }, + custom: async (tokens, execShellCommand) => { + // The default username is the current user + let username = await execShellCommand("whoami"); + + // Suggests servers if a username has been provided, instead of displaying nothing + for (let i = 0; i < tokens.length; i++) { + if (tokens[i].includes("@")) { + username = tokens[i].split("@")[0]; + break; + } + } + + // Get all servers this user has access to + const out = await execShellCommand("tsh ls --format=json"); + return JSON.parse(out).map((elm) => { + const connection_string = + username.length > 0 + ? `${username}@${elm.spec.hostname}` + : `${elm.spec.hostname}`; + return { - name: elm.spec.hostname, - description: `Access expires: ${elm.metadata.expires}`, + name: connection_string, + description: `Connect to ${elm.spec.hostname} as ${username}`, }; }); //[{ name: "hello" }]; }, From 0d9ee614e2929605064898d88f67256e29a7a6a7 Mon Sep 17 00:00:00 2001 From: Kane Petra Date: Thu, 19 Oct 2023 13:39:18 +0200 Subject: [PATCH 2/4] feat(tctl): full command support --- src/tctl.ts | 1791 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1791 insertions(+) create mode 100644 src/tctl.ts diff --git a/src/tctl.ts b/src/tctl.ts new file mode 100644 index 000000000000..f3d8ae3c3203 --- /dev/null +++ b/src/tctl.ts @@ -0,0 +1,1791 @@ +const teleportAccountsGenerator: Fig.Generator = { + script: "tctl get users --format json", + postProcess: function (out) { + const users = JSON.parse(out); + return users.map((user: any) => { + return { + name: user.metadata.name, + description: user.metadata.description + } + }) + } +} + +const teleportKubernetesClustersGenerator: Fig.Generator = { + script: "tsh kube ls --format json", + postProcess: function (out) { + const clusters = JSON.parse(out); + return clusters.map((cluster: any) => { + return { + name: cluster.kube_cluster_name, + description: "Kubernetes cluster connected to Teleport" + } + }) + } +} + +const commaQueryTerm = (curr) => { + if (curr.includes(",")) { + return curr.slice(curr.lastIndexOf(",") + 1); + } else { + return curr; + } +} + +const flatArrayToTeleportRoles = (roles: string[]): { "metadata": { "name": string } }[] => { + return roles.map((role) => { + return { + "metadata": { + "name": role + } + } + }) +} + +const teleportRolesToFlatArray = (roles: { "metadata": { "name": string } }[]): string[] => { + return roles.map((role) => { + return role.metadata.name; + }) +} + +const filterRoles = (currentRoles: string, allRoles: { "metadata": { "name": string, "description": string } }[]) => { + let currentRolesString = currentRoles; + + if (currentRoles.includes("=")) { + currentRolesString = currentRoles.split("=")[1]; + } + + const filterable = currentRolesString.split(","); + + return allRoles.filter((role: any) => { + return !filterable.includes(role.metadata.name); + }) +} + +const teleportRolesGenerator: Fig.Generator = { + script: "tctl get roles --format json", + getQueryTerm: commaQueryTerm, + postProcess: function (out, tokens) { + const roles = JSON.parse(out); + + const filteredRoles = filterRoles(tokens[tokens.length - 1], roles); + + return filteredRoles.map((role: any) => { + return { + name: role.metadata.name, + description: role.metadata.description + } + }) + } +} + +const teleportAppGenerator: Fig.Generator = { + script: "tsh apps ls --format json", + postProcess: function (out) { + const apps = JSON.parse(out); + return apps.map((app: any) => { + return { + name: app.metadata.name, + description: app.metadata.description + } + }) + } +} + + +const teleportDatabaseGenerator: Fig.Generator = { + script: "tsh db ls --format json", + postProcess: function (out) { + const dbs = JSON.parse(out); + return dbs.map((db: any) => { + return { + name: db.metadata.name, + description: db.metadata.description + } + }) + } +} + +const teleportRequestGenerator: Fig.Generator = { + script: "tctl request ls --format json", + postProcess: function (out) { + const requests = JSON.parse(out); + return requests.map((request: any) => { + return { + name: request.metadata.name, + description: request.metadata.description + } + }) + } +} + +const teleportTokenGenerator: Fig.Generator = { + script: "tctl tokens ls --format json", + postProcess: function (out) { + const tokens = JSON.parse(out); + return tokens.map((token: any) => { + return { + name: token.metadata.name, + description: token.metadata.description + } + }) + } +} + +const teleportWindowsDesktopGenerator: Fig.Generator = { + script: "tctl get windows_desktop --format json", + postProcess: function (out) { + const desktops = JSON.parse(out); + return desktops.map((desktop: any) => { + return { + name: desktop.metadata.name, + description: desktop.metadata.description + } + }) + } +} + +const teleportFormatOption: Fig.Option = { + name: "--format", + description: "Output format. One of: [text, json, yaml]", + args: { + name: "format", + suggestions: ["text", "json", "yaml"], + default: "text" + } +} + +const teleportBotsGenerator: Fig.Generator = { + script: "tctl bots ls --format json", + postProcess: function (out) { + const bots = JSON.parse(out); + return bots.map((bot: any) => { + return { + name: bot.metadata.name.slice(4), + description: bot.metadata.description + } + }) + } +} + +const teleportACLGenerator: Fig.Generator = { + script: "tctl acl ls --format json", + postProcess: function (out) { + const acl = JSON.parse(out); + return acl.map((acl: any) => { + return { + name: acl.metadata.name, + description: acl.metadata.description + } + }) + } +} + +const teleportAlertGenerator: Fig.Generator = { + script: "tctl alerts list --format json", + postProcess: function (out) { + const alerts = JSON.parse(out); + return alerts.map((alert: any) => { + return { + name: alert.metadata.name, + description: alert.metadata.description + } + }) + } +} + +const teleportDeviceGenerator: Fig.Generator = { + script: "tctl get device --format json", + postProcess: function (out) { + const devices = JSON.parse(out); + return devices.map((device: any) => { + return { + name: device.metadata.name, + description: device.metadata.description + } + }) + } +} + +const teleportSAMLConnectorGenerator: Fig.Generator = { + script: "tctl get connector --format json", + postProcess: function (out) { + const connectors = JSON.parse(out); + return connectors.map((connector: any) => { + return { + name: connector.metadata.name, + description: connector.metadata.description + } + }) + } +} + +const teleportGetResourcesGenerator: Fig.Generator = { + trigger: (current, old) => { + return true; + }, + custom: async (tokens, executeShellCommand) => { + const standardSuggestions = ["user", "role", "connector", "node", "windows_desktop", "cluster", "login_rule", "device", "ui_config", "cluster_auth_preference", "lock", "all"] + let respondSuggestions = standardSuggestions.map((suggestion) => { + return { + name: suggestion, + description: "Get a " + suggestion + } + }); + + if (tokens.find((token) => standardSuggestions.includes(token.split("/")[0])) != undefined) { + // Get the name from the token which contains one of the standard suggestions + const resource = tokens.find((token) => standardSuggestions.includes(token.split("/")[0])).split("/")[0]; + + if (standardSuggestions.find((sug) => sug === resource) == undefined) return respondSuggestions; + if (["cluster_auth_preference", "all"].includes(resource)) return []; // This is what tctl expects + + const resources = await executeShellCommand(`tctl get ${resource} --format json`); + const parsedResources = JSON.parse(resources); + + return parsedResources.map((parsedResource: any) => { + return { + name: `${resource}/${parsedResource.metadata.name}` + } + }) + } + + return respondSuggestions; + } +} + +/* tctl lock --help + --user Name of a Teleport user to disable. + --role Name of a Teleport role to disable. + --login Name of a local UNIX user to disable. + --mfa-device UUID of a user MFA device to disable. + --windows-desktop Name of a Windows desktop to disable. + --access-request UUID of an access request to disable. + --device UUID of a trusted device to disable. + --message Message to display to locked-out users. + --expires Time point (RFC3339) when the lock expires. + --ttl Time duration after which the lock expires. + --server-id UUID of a Teleport server to disable. +*/ + +const completionSpec: Fig.Spec = { + name: "tctl", + description: "Admin tool for the Teleport Access Platform", + subcommands: [ + /* tctl help */ + { + name: "help", + description: "Show help.", + }, + /* tctl users */ + { + "name": "users", + subcommands: [ + { + name: "add", + description: "Add a new user.", + args: [ + { + name: "account", + description: "Teleport user account name.", + } + ], + options: [ + { + name: "--logins", + description: "List of allowed SSH logins for the new user.", + }, + { + name: "--windows-logins", + description: "List of allowed Windows logins for the new user.", + }, + { + name: "--kubernetes-users", + description: "List of allowed Kubernetes users for the new user.", + }, + { + name: "--kubernetes-groups", + description: "List of allowed Kubernetes groups for the new user.", + }, + { + name: "--db-users", + description: "List of allowed database users for the new user.", + }, + { + name: "--db-names", + description: "List of allowed database names for the new user.", + }, + { + name: "--db-roles", + description: "List of database roles for automatic database user provisioning." + }, + { + name: "--aws-role-arns", + description: "List of allowed AWS role ARNs for the new user.", + }, + { + name: "--azure-identities", + description: "List of allowed Azure identities for the new user.", + }, + { + name: "--gcp-service-accounts", + description: "List of allowed GCP service accounts for the new user.", + }, + { + name: "--host-user-uid", + description: "UID for auto provisioned host users to use.", + }, + { + name: "--host-user-gid", + description: "GID for auto provisioned host users to use.", + }, + { + name: "--ttl", + description: "Set expiration time for token, default is 1h0m0s, maximum is 48h0m0s.", + }, + { + name: "--roles", + description: "List of roles for the new user to assume. Comma seperated.", + isRequired: true, + isRepeatable: true, + args: { + generators: teleportRolesGenerator, + } + } + ], + }, + { + name: "update", + description: "Update user account.", + options: [ + { + name: "--set-roles", + description: "List of roles for the user to assume, replaces current roles. Comma seperated.", + args: { + generators: teleportRolesGenerator + } + }, + { + name: "--set-logins", + description: "List of allowed SSH logins for the user, replaces current logins.", + }, + { + name: "--set-windows-logins", + description: "List of allowed Windows logins for the user, replaces current Windows logins.", + }, + { + name: "--set-kubernetes-users", + description: "List of allowed Kubernetes users for the user, replaces current Kubernetes users.", + }, + { + name: "--set-kubernetes-groups", + description: "List of allowed Kubernetes groups for the user, replaces current Kubernetes groups.", + }, + { + name: "--set-db-users", + description: "List of allowed database users for the user, replaces current database users.", + }, + { + name: "--set-db-names", + description: "List of allowed database names for the user, replaces current database names.", + }, + { + name: "--set-db-roles", + description: "List of allowed database roles for automatic database user provisioning, replaces current database roles.", + }, + { + name: "--set-aws-role-arns", + description: "List of allowed AWS role ARNs for the user, replaces current AWS role ARNs.", + }, + { + name: "--set-azure-identities", + description: "List of allowed Azure identities for the user, replaces current Azure identities.", + }, + { + name: "--set-gcp-service-accounts", + description: "List of allowed GCP service accounts for the user, replaces current service accounts.", + }, + { + name: "--set-host-user-uid", + description: "UID for auto provisioned host users to use. Value can be reset by providing an empty string.", + }, + { + name: "--set-host-user-gid", + description: "GID for auto provisioned host users to use. Value can be reset by providing an empty string.", + } + ], + args: { + name: "account", + description: "Teleport user account name.", + generators: teleportAccountsGenerator + } + }, + { + name: "ls", + description: "Lists all user accounts.", + }, + { + name: "rm", + description: "Remove user account.", + args: { + name: "account", + description: "Teleport user account name.", + isVariadic: true, + generators: teleportAccountsGenerator + } + }, + { + name: "reset", + description: "Reset user password and generate a new token [Teleport DB users only].", + args: { + name: "account", + description: "Teleport user account name.", + generators: teleportAccountsGenerator + } + } + ] + }, + /* tctl nodes */ + { + name: "nodes", + description: "Issue invites for other nodes to join the cluster.", + subcommands: [ + { + name: "add", + description: "Generate a node invitation token.", + options: [ + { + name: "--roles", + description: "Comma-separated list of roles for the new node to assume", + args: { + generators: { + getQueryTerm: commaQueryTerm, + trigger: (current, old) => { + return true; + }, + custom: async (tokens) => { + const roles = ["node", "proxy", "auth", "app", "db"] + + const filteredRoles = filterRoles(tokens[tokens.length - 1], roles.map((role) => { + return { + "metadata": { + "name": role, + "description": "Add a new " + role + " to the cluster" + } + } + })); + + console.log(filterRoles) + + return filteredRoles.map((role: any) => { + return { + name: role.metadata.name, + } + }) + } + } + } + }, + { + name: "--ttl", + description: "Time to live for a generated token, default is 0h30m0s, maximum is 48h0m0s.", + } + ] + }, + { + name: "ls", + description: "List all active SSH nodes within the cluster.", + options: [ + { + name: "--namespace", + description: "Namespace of the nodes", + args: { + name: "namespace", + description: "Namespace of the nodes", + isOptional: true, + generators: { + script: "tctl get namespaces --format json", + postProcess: function (out) { + const namespaces = JSON.parse(out); + return namespaces.map((namespace: any) => { + return { + name: namespace.metadata.name, + description: namespace.metadata.description + } + }) + } + } + } + } + ] + } + ] + }, + /* tctl tokens */ + { + name: "tokens", + description: "Manage invitation tokens.", + subcommands: [ + { + name: "add", + description: "Create a invitation token.", + args: {}, + options: [ + teleportFormatOption, + { + name: "--type", + description: "Type(s) of token to add", + isRepeatable: true, + requiresSeparator: true, + args: { + name: "type", + description: "node,app,db,proxy,etc", + default: "app", + isOptional: false, + generators: { + getQueryTerm: commaQueryTerm, + custom: async (tokens) => { + const types = [ + { + "metadata": { + "name": "proxy", + "description": "Add a new proxy node to the cluster" + } + }, + { + "metadata": { + "name": "auth", + "description": "Add a new auth service node to the cluster" + } + }, + { + "metadata": { + "name": "trusted_cluster", + "description": "Add a new trusted cluster to the cluster" + } + }, + { + "metadata": { + "name": "node", + "description": "Aadd a new (ssh-)node to the cluster" + } + }, + { + "metadata": { + "name": "db", + "description": "Add a new database to the cluster" + } + }, + { + "metadata": { + "name": "kube", + "description": "Add a new kubernetes cluster to the cluster" + } + }, + { + "metadata": { + "name": "app", + "description": "Add a new (web-)application to the cluster" + } + }, + { + "metadata": { + "name": "windowsdesktop", + "description": "Add a new windows desktop to the cluster" + } + } + ] + + const filteredTypes = filterRoles(tokens[tokens.length - 1], types); + + return filteredTypes.map((type) => { + return { + name: type.metadata.name, + description: type.metadata.description + } + }) + } + } + } + }, + { + name: "--value", + description: "Override the default random generated token with a specified value", + args: { + name: "value", + } + }, + { + name: "--labels", + description: "Set token labels", + args: { + name: "label1=value1,label2=value2", + } + }, + { + name: "--ttl", + description: "Set expiration time for token, default is 30 minutes", + args: { + name: "30m", + } + }, + { + name: "--app-name", + description: "Name of the application to add", + args: { + name: "name", + } + }, + { + name: "--app-uri", + description: "URI of the application to add", + args: { + name: "app.example.com", + } + }, + { + name: "--db-name", + description: "Name of the database to add", + args: { + name: "name", + } + }, + { + name: "--db-protocol", + description: "Database protocol to use. Supported are: [postgres mysql mongodb oracle cockroachdb redis snowflake sqlserver cassandra elasticsearch opensearch dynamodb clickhouse clickhouse-http]", + args: { + name: "protocol", + suggestions: ["postgres", "mysql", "mongodb", "oracle", "cockroachdb", "redis", "snowflake", "sqlserver", "cassandra", "elasticsearch", "opensearch", "dynamodb", "clickhouse", "clickhouse-http"], + default: "postgres", + } + } + ] + }, + { + name: ["rm", "del"], + description: "Delete/revoke an invitation token.", + args: { + name: "token", + description: "Token to delete.", + isVariadic: true, + generators: { + script: "tctl get tokens --format json", + postProcess: function (out) { + const tokens = JSON.parse(out); + return tokens.map((token: any) => { + return { + name: token.metadata.name, + description: token.metadata.description + } + }) + } + } + } + }, + { + name: "ls", + description: "List node and user invitation tokens.", + options: [teleportFormatOption] + } + ] + }, + /* tctl auth */ + { + name: "auth", + description: "Operations with user and host certificate authorities (CAs).", + args: {}, + subcommands: [ + { + name: "export", + description: "Export public cluster (CA) keys to stdout.", + options: [ + { + name: "--keys", + description: "If set, will print private keys", + }, + { + name: "--fingerprint", + description: "Filter authority by fingerprint" + }, + { + name: "--compat", + description: "Export certificates compatible with specific version of Teleport", + args: { + name: "version", + description: "E.g. 13" + } + }, + { + name: "--type", + description: "Export certificate type", + args: { + name: "type", + suggestions: ["user", "host", "tls-host", "tls-user", "tls-user-der", "windows", "db", "db-der", "openssh", "saml-idp"], + description: "Export certificate type", + } + } + ] + }, + { + name: "sign", + description: "Create an identity file(s) for a given user.", + args: {}, + options: [ + { + name: "--user", + description: "Teleport user name", + isRequired: true, + args: { + name: "user", + generators: teleportAccountsGenerator + } + }, + { + name: "--host", + description: "Teleport host name", + args: { + name: "host", + } + }, + { + name: ["--out", "-o"], + description: "Identity output", + isRequired: true, + args: { + name: "out", + template: "filepaths" + } + }, + { + name: "--format", + description: "Identity format", + args: { + name: "format", + suggestions: ["file", "openssh", "tls", "kubernetes"], + default: "file", + } + }, + { + name: "--ttl", + description: "TTL (time to live) for the generated certificate.", + args: { + name: "ttl", + } + }, + { + name: "--compat", + description: "OpenSSH compatibility flag", + }, + { + name: "--proxy", + description: "Address of the Teleport proxy. When --format is set to \"kubernetes\", this address will be set as cluster address in the generated kubeconfig file", + args: { + name: "proxy", + } + }, + { + name: "--overwrite", + description: "Whether to overwrite existing destination files. When not set, user will be prompted before overwriting any existing file.", + }, + { + name: "--no-overwrite", + description: "Whether to overwrite existing destination files. When not set, user will be prompted before overwriting any existing file.", + }, + { + name: "--tar", + description: "Create a tarball of the resulting certificates and stream to stdout.", + }, + { + name: "--leaf-cluster", + description: "Leaf cluster to generate identity file for when --format is set to \"kubernetes\"", + args: { + name: "leaf-cluster", + } + }, + { + name: "--kube-cluster-name", + description: "Kubernetes cluster to generate identity file for when --format is set to \"kubernetes\"", + args: { + name: "name", + generators: teleportKubernetesClustersGenerator + } + }, + { + name: "--app-name", + description: "Application to generate identity file for. Mutually exclusive with \"--db-service\".", + args: { + name: "name", + generators: teleportAppGenerator + } + }, + { + name: "--db-service", + description: "Database to generate identity file for. Mutually exclusive with \"--app-name\".", + args: { + name: "service", + generators: teleportDatabaseGenerator + } + }, + { + name: "--db-user", + description: "Database user placed on the identity file. Only used when \"--db-service\" is set.", + }, + { + name: "--db-name", + description: "Database name placed on the identity file. Only used when \"--db-service\" is set.", + }, + { + name: "--windows-user", + description: "Window user placed on the identity file. Only used when --format is set to \"windows\"", + }, + { + name: "--windows-domain", + description: "Active Directory domain for which this cert is valid. Only used when --format is set to \"windows\"", + }, + { + name: "--windows-sid", + description: "Optional Security Identifier to embed in the certificate. Only used when --format is set to \"windows\"", + } + ] + }, + { + name: "rotate", + description: "Rotate certificate authorities in the cluster.", + options: [ + { + name: "--grace-period", + description: "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to re-login and nodes to re-register.", + args: { + name: "duration", + description: "relative duration like 5s, 2m, or 3h" + } + }, + { + name: "--manual", + description: "Activate manual rotation, set rotation phases manually", + }, + { + name: "--type", + description: "Certificate authority to rotate", + isRequired: true, + args: { + name: "type", + suggestions: ["host", "user", "db", "openssh", "jwt", "saml_idp", "oidc_idp"], + default: "host", + } + }, + { + name: "--phase", + description: "Target rotation phase to set, used in manual rotation", + args: { + name: "phase", + suggestions: ["init", "standby", "update_clients", "update_servers", "rollback"], + default: "init", + } + } + ] + }, + { + name: "ls", + description: "List connected auth servers.", + options: [teleportFormatOption] + }, + { + name: "crl", + description: "Export empty certificate revocation list (CRL) for certificate authorities.", + options: [ + { + name: "--type", + description: "Certificate authority to rotate", + isRequired: true, + args: { + name: "type", + suggestions: ["host", "db",], + default: "host", + } + } + ] + } + ] + }, + /* tctl get */ + { + name: "get", + description: "Get a resource.", + args: { + name: "resource", + description: "Resource to get", + generators: teleportGetResourcesGenerator + }, + options: [ + teleportFormatOption, + { + name: "--with-secrets", + description: "Include secrets in resources like certificate authorities or OIDC connectors", + }, + { + name: "--verbose", + description: "Verbose table output, shows full label output", + } + ] + }, + /* tctl status */ + { + name: "status", + description: "Report cluster status.", + }, + /* tctl top */ + { + name: "top", + description: "Report cluster status.", + args: [ + { + name: "diag-address", + description: "Diagnostic HTTP URL, default is http://127.0.0.1:3000", + }, + { + name: "refresh", + description: "Refresh period" + } + ] + }, + /* tctl requests */ + { + name: ["requests", "request"], + description: "Manage access requests.", + args: {}, + subcommands: [ + { + name: "ls", + description: "Show active access requests." + }, + { + name: "get", + description: "Show access request details.", + args: { + name: "request", + description: "Access request ID", + generators: teleportRequestGenerator + } + }, + { + name: "approve", + description: "Approve pending access request.", + args: { + name: "request", + description: "Access request ID", + generators: teleportRequestGenerator + } + }, + { + name: "deny", + description: "Deny pending access request.", + args: { + name: "request", + description: "Access request ID", + generators: teleportRequestGenerator + } + }, + { + name: "create", + description: "Create pending access request.", + args: { + name: "username", + description: "Name of target user", + generators: teleportAccountsGenerator + }, + options: [ + { + name: "--roles", + description: "Roles to be requested", + args: { + name: "roles", + generators: teleportRolesGenerator + } + }, + { + name: "--reason", + description: "Optional reason message", + }, + { + name: "--resource", + description: "Resource ID to be requested", + args: { + name: "resource", + generators: teleportGetResourcesGenerator + } + }, + { + name: "--dry-run", + description: "Don't actually generate the access request" + } + ] + }, + { + name: "rm", + description: "Delete an access request.", + options: [ + { + name: ["--force", "-f"], + description: "Force the deletion of an active access request" + } + ], + args: { + name: "request-id", + description: "Access request ID", + generators: teleportRequestGenerator + } + }, + { + name: "review", + description: "Review an access request.", + options: [ + { + name: "--author", + description: "Username of reviewer", + isRequired: true, + args: { + name: "author", + generators: teleportAccountsGenerator + } + }, + { + name: "--approve", + description: "Review proposes approval" + }, + { + name: "--deny", + description: "Review proposes denial" + }, + ], + args: { + name: "request-id", + description: "Access request ID", + generators: teleportRequestGenerator + } + } + ] + }, + /* tctl apps */ + { + name: "apps", + description: "Operate on applications registered with the cluster.", + args: {}, + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List all applications registered with the cluster.", + } + ] + }, + /* tctl db */ + { + name: "db", + description: "Operate on databases registered with the cluster.", + args: {}, + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List all databases registered with the cluster.", + } + ] + }, + /* tctl kube */ + { + name: "kube", + description: "Operate on registered Kubernetes clusters.", + args: {}, + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List all Kubernetes clusters registered with the cluster.", + } + ] + }, + /* tctl windows_desktops */ + { + name: "windows_desktops", + description: "Operate on registered Windows desktops.", + args: {}, + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List all Windows desktops registered with the cluster.", + } + ] + }, + /* tctl proxy */ + { + name: "proxy", + description: "Operations with information for cluster proxies.", + args: {}, + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "Lists proxies connected to the cluster.", + options: [teleportFormatOption] + } + ] + }, + /* tctl rm */ + { + name: ["rm", "del"], + description: "Delete a resource.", + args: { + name: "resource type/resource name", + description: "Resource to delete", + generators: teleportGetResourcesGenerator + }, + }, + /* tctl lock */ + { + name: "lock", + description: "Create a new lock.", + args: {}, + options: [ + { + name: "--user", + description: "Name of a Teleport user to disable.", + args: { + name: "user", + generators: teleportAccountsGenerator + } + }, + { + name: "--role", + description: "Name of a Teleport role to disable.", + args: { + name: "role", + generators: teleportRolesGenerator + } + }, + { + name: "--login", + description: "Name of a local UNIX user to disable.", + }, + { + name: "--mfa-device", + description: "UUID of a user MFA device to disable.", + }, + { + name: "--windows-desktop", + description: "Name of a Windows desktop to disable.", + args: { + name: "desktop", + generators: teleportWindowsDesktopGenerator + } + }, + { + name: "--access-request", + description: "UUID of an access request to disable.", + args: { + name: "request", + generators: teleportRequestGenerator + } + }, + { + name: "--device", + description: "UUID of a trusted device to disable.", + }, + { + name: "--message", + description: "Message to display to locked-out users.", + }, + { + name: "--expires", + description: "Time point (RFC3339) when the lock expires.", + args: { + name: "yyyy-MM-ddThh:mm:ss.msZ", + description: "Time point (RFC3339) when the lock expires.", + } + }, + { + name: "--ttl", + description: "Time duration after which the lock expires.", + args: { + name: "duration", + description: "Time duration after which the lock expires.", + } + }, + { + name: "--server-id", + description: "UUID of a Teleport server to disable.", + } + ] + }, + /* tctl bots */ + { + name: "bots", + description: "Operate on certificate renewal bots registered with the cluster.", + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List all certificate renewal bots registered with the cluster.", + }, + { + name: "add", + description: "Add a new certificate renewal bot to the cluster.", + args: { + name: "name", + description: "A name to uniquely identify this bot in the cluster.", + }, + options: [ + { + name: "--roles", + description: "Roles the bot is able to assume.", + isRequired: true, + args: { + name: "roles", + generators: teleportRolesGenerator + } + }, + { + name: "--ttl", + description: "TTL for the bot join token.", + }, + { + name: "--token", + description: "Name of an existing token to use.", + args: { + name: "token", + generators: teleportTokenGenerator + } + }, + { + name: "--logins", + description: "List of allowed SSH logins for the bot user", + } + ] + }, + { + name: "rm", + description: "Permanently remove a certificate renewal bot from the cluster.", + args: { + name: "name", + description: "Name of an existing bot to remove.", + generators: teleportBotsGenerator + } + } + ] + }, + /* tctl inventory */ + { + name: "inventory", + description: "Manage Teleport instance inventory.", + requiresSubcommand: true, + subcommands: [ + { + name: "status", + description: "Show inventory status summary.", + options: [ + { + name: "--connected", + description: "Show locally connected instances summary" + } + ] + }, + { + name: ["list", "ls"], + description: "List Teleport instance inventory.", + options: [ + { + name: "--older-than", + description: "Filter for older teleport versions", + args: { + name: "version", + description: "E.g. 13" + } + }, + { + name: "--newer-than", + description: "Filter for newer teleport versions", + args: { + name: "version", + description: "E.g. 13" + } + }, + { + name: "--exact-version", + description: "Filter for exact teleport version", + args: { + name: "version", + description: "E.g. 13" + } + }, + { + name: "--services", + description: "Filter output by service (node,kube,proxy,etc)", + args: { + name: "service", + description: "E.g. node", + suggestions: ["node", "kube", "proxy", "auth", "app", "db", "windowsdesktop"] + } + }, + { + name: "--upgrader", + description: "Filter output by upgrader (kube,unit,none)", + args: { + name: "upgrader", + description: "E.g. kube", + suggestions: ["kube", "unit", "none"] + } + } + ] + } + ] + }, + /* tctl recordings */ + { + name: "recordings", + description: "View and control session recordings.", + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List recorded sessions.", + } + ] + }, + /* tctl alerts */ + { + name: "alerts", + description: "Manage cluster alerts.", + requiresSubcommand: true, + subcommands: [ + { + name: "list", + description: "List cluster alerts.", + options: [ + teleportFormatOption, + { + name: "--labels", + description: "List of comma separated labels to filter by labels", + args: { + name: "label1=value1,label2=value2", + } + }, + { + name: ["--verbose", "-v"], + description: "Show detailed alert info, including acknowledged alerts." + } + ] + }, + { + name: "create", + description: "Create cluster alerts.", + options: [ + { + name: "--labels", + description: "List of comma separated labels to filter by labels", + args: { + name: "label1=value1,label2=value2", + } + }, + { + name: "--severity", + description: "Severity of the alert (low, medium, or high).", + args: { + name: "severity", + suggestions: ["low", "medium", "high"] + } + }, + { + name: "--ttl", + description: "Time duration after which the alert expires (default 24h).", + } + ], + args: { + name: "message", + description: "Alert body message", + } + }, + { + name: "ack", + description: "Acknowledge cluster alerts.", + subcommands: [ + { + name: "ls", + description: "List acknowledged cluster alerts.", + } + ], + args: { + name: "id", + description: "The cluster alert ID.", + generators: teleportAlertGenerator + }, + options: [ + { + name: "--reason", + description: "The reason for acknowledging the cluster alert.", + }, + { + name: "--ttl", + description: "Time duration after which the alert expires (default 24h).", + }, + { + name: "--clear", + description: "Clear the acknowledgment for the cluster alert." + } + ] + }, + ] + }, + /* tctl create */ + { + name: "create", + description: "Create or update a Teleport resource from a YAML file.", + args: { + name: "filename", + template: "filepaths", + }, + options: [ + { + name: ["--force", "-f"], + description: "Overwrite the resource if already exists." + } + ] + }, + /* tctl update */ + { + name: "update", + description: "Update resource fields.", + args: { + name: "resource type/resource name", + description: "Resource to update", + generators: teleportGetResourcesGenerator + }, + options: [ + { + name: "--set-labels", + description: "Set labels", + }, + { + name: "--set-ttl", + description: "Set TTL", + } + ] + }, + /* tctl edit */ + { + name: "edit", + description: "Edit a Teleport resource.", + args: { + name: "resource type/resource name", + description: "Resource to edit", + generators: teleportGetResourcesGenerator + }, + }, + /* tctl devices */ + { + name: "devices", + description: "Register and manage trusted devices.", + requiresSubcommand: true, + subcommands: [ + { + name: "add", + description: "Register managed devices.", + options: [ + { + name: "--os", + description: "Operating System", + exclusiveOn: ["--current-device"] + }, + { + name: "--asset-tag", + description: "Inventory identifier for the device (e.g., Mac serial number)", + exclusiveOn: ["--current-device"] + }, + { + name: "--current-device", + description: "Registers the current device. Overrides --os and --asset-tag.", + exclusiveOn: ["--os", "--asset-tag"] + }, + { + name: "--enroll", + description: "If set, creates a device enrollment token.", + }, + { + name: "--enroll-ttl", + description: "Time duration for the enrollment token", + dependsOn: ["--enroll"], + } + ] + }, + { + name: "ls", + description: "Lists managed devices." + }, + { + name: "rm", + description: "Removes a managed device.", + args: { + name: "device", + description: "Device ID", + generators: teleportDeviceGenerator + } + }, + { + name: "enroll", + description: "Creates a new device enrollment token.", + options: [ + { + name: "--asset-tag", + description: "Inventory identifier for the device (e.g., Mac serial number)", + exclusiveOn: ["--current-device"] + }, + { + name: "--current-device", + description: "Registers the current device. Overrides --os and --asset-tag.", + exclusiveOn: ["--os", "--asset-tag"] + }, + ] + }, + { + name: "lock", + description: "Locks a device.", + args: { + name: "device", + description: "Device ID", + generators: teleportDeviceGenerator + } + } + ] + }, + /* tctl saml */ + { + name: "saml", + description: "Operations on SAML auth connectors.", + requiresSubcommand: true, + subcommands: [ + { + name: "export", + description: "Export a SAML signing key in .crt format.", + args: { + name: "connector_name", + description: "Name of the SAML connector to export the key from", + generators: teleportSAMLConnectorGenerator, + } + } + ] + }, + /* tctl acl */ + { + name: ["acl", "access-lists"], + description: "Manage access lists.", + requiresSubcommand: true, + subcommands: [ + { + name: "ls", + description: "List cluster access lists.", + }, + { + name: "get", + description: "Get detailed information for an access list.", + args: { + name: "access list", + description: "Access list name", + generators: teleportACLGenerator + } + }, + { + name: "users", + description: "Manage user membership to access lists.", + requiresSubcommand: true, + subcommands: [ + { + name: "add", + description: "Add a user to an access list.", + args: [ + { + name: "access-list-name", + description: "The access list name", + generators: teleportACLGenerator + }, + { + name: "user", + description: "The user name", + generators: teleportAccountsGenerator + }, + { + name: "expires", + description: "When the user's access expires (must be in RFC3339). Defaults to the expiration time of the access list.", + isOptional: true, + }, + { + name: "reason", + description: "The reason the user has been added to the access list. Defaults to empty.", + isOptional: true, + } + ] + }, + { + name: "rm", + description: "Remove a user from an access list.", + args: [ + { + name: "access-list-name", + description: "The access list name", + generators: teleportACLGenerator + }, + { + name: "user", + description: "The user name", + generators: teleportAccountsGenerator + } + ] + }, + { + name: "ls", + description: "List users that are members of an access list.", + args: { + name: "access list", + description: "Access list name", + generators: teleportACLGenerator + } + } + ] + } + ] + }, + /* tctl login_rule */ + { + name: "login_rule", + description: "Test login rules", + requiresSubcommand: true, + subcommands: [ + { + name: "test", + description: "Test the parsing and evaluation of login rules.", + } + ] + }, + /* tctl sso */ + { + name: "sso", + description: "A family of commands for configuring and testing auth connectors (SSO).", + requiresSubcommand: true, + subcommands: [ + { + name: "configure", + description: "Create auth connector configuration.", + requiresSubcommand: true, + subcommands: [ + { + name: "github", + description: "Configure GitHub auth connector.", + options: [ + { + name: "--name", + description: "Connector name.", + }, + { + name: "--teams-to-roles", + description: "Sets teams-to-roles mapping using format 'organization,name,role1,role2,...'. Repeatable.", + args: { + name: "teams-to-roles", + } + }, + { + name: "--display", + description: "Sets the connector display name.", + }, + { + name: "--id", + description: "GitHub app client ID", + }, + { + name: "--secret", + description: "GitHub app client secret", + }, + { + name: "--endpoint-url", + description: "Endpoint URL for GitHub instance.", + }, + { + name: "--api-endpoint-url", + description: "API endpoint URL for GitHub instance.", + }, + { + name: "--redirect-url", + description: "Authorization callback URL.", + }, + { + name: "--ignore-missing-roles", + description: "Ignore missing roles referenced in --teams-to-roles.", + dependsOn: ["--teams-to-roles"], + } + ] + } + ] + }, + { + name: "test", + description: "Perform end-to-end test of SSO flow using provided auth connector definition.", + args: { + name: "filename", + description: "Connector resource definition filename. Empty for stdin.", + isOptional: true, + template: "filepaths", + } + }, + ] + }, + /* tctl version */ + { + name: "version", + description: "Print the version of your tctl binary." + } + ], + options: [ + { + name: ["--help", "-h"], + description: "Show help.", + isPersistent: true, + }, + { + name: ["--debug", "-d"], + description: "Enable verbose logging to stderr", + isPersistent: true, + }, + { + name: ["--config", "-c"], + description: "Path to a configuration file [/etc/teleport.yaml].", + isPersistent: true, + args: { + name: "config", + template: "filepaths" + }, + }, + { + name: ["--auth-server"], + description: "Attempts to connect to specific auth/proxy address(es)", + isPersistent: true, + args: { + name: "auth-server", + } + }, + { + name: ["--identity", "-i"], + description: "Path to an identity file.", + isPersistent: true, + args: { + name: "identity", + template: "filepaths" + }, + }, + { + name: ["--insecure"], + description: "When specifying a proxy address in --auth-server, do not verify its TLS certificate.", + isPersistent: true, + } + ], + // Only uncomment if tctl takes an argument + // args: {} +}; +export default completionSpec; \ No newline at end of file From bf15bff308d1f9101df77cafb92cc2c2cde43266 Mon Sep 17 00:00:00 2001 From: Kane Petra Date: Thu, 19 Oct 2023 14:22:37 +0200 Subject: [PATCH 3/4] tweak(tctl): proper types --- src/tctl.ts | 1372 +++++++++++++++++++++++++++++---------------------- 1 file changed, 769 insertions(+), 603 deletions(-) diff --git a/src/tctl.ts b/src/tctl.ts index f3d8ae3c3203..cdc5bb6e93e8 100644 --- a/src/tctl.ts +++ b/src/tctl.ts @@ -1,28 +1,70 @@ +import { type } from "node:os"; + +// Barebones Teleport resource definitions, ensuring type safety and autocomplete +type GenericResource = { + metadata: { + name: string; + description: string; + }; +}; + +type User = object & GenericResource; + +type Role = object & GenericResource; + +type App = object & GenericResource; + +type Db = object & GenericResource; + +type AccessRequest = object & GenericResource; + +type Token = object & GenericResource; + +type Bot = object & GenericResource; + +type WindowsDesktop = object & GenericResource; + +type ACL = object & GenericResource; + +type Alert = object & GenericResource; + +type Device = object & GenericResource; + +type Connector = object & GenericResource; + +type Namespace = object & GenericResource; + +type Cluster = { + kube_cluster_name: string; +}; + const teleportAccountsGenerator: Fig.Generator = { script: "tctl get users --format json", postProcess: function (out) { const users = JSON.parse(out); - return users.map((user: any) => { + + return users.map((user: User) => { return { name: user.metadata.name, - description: user.metadata.description - } - }) - } -} + description: user.metadata.description, + }; + }); + }, +}; const teleportKubernetesClustersGenerator: Fig.Generator = { script: "tsh kube ls --format json", postProcess: function (out) { const clusters = JSON.parse(out); - return clusters.map((cluster: any) => { + + return clusters.map((cluster: Cluster) => { return { name: cluster.kube_cluster_name, - description: "Kubernetes cluster connected to Teleport" - } - }) - } -} + description: "Kubernetes cluster connected to Teleport", + }; + }); + }, +}; const commaQueryTerm = (curr) => { if (curr.includes(",")) { @@ -30,25 +72,9 @@ const commaQueryTerm = (curr) => { } else { return curr; } -} - -const flatArrayToTeleportRoles = (roles: string[]): { "metadata": { "name": string } }[] => { - return roles.map((role) => { - return { - "metadata": { - "name": role - } - } - }) -} - -const teleportRolesToFlatArray = (roles: { "metadata": { "name": string } }[]): string[] => { - return roles.map((role) => { - return role.metadata.name; - }) -} +}; -const filterRoles = (currentRoles: string, allRoles: { "metadata": { "name": string, "description": string } }[]) => { +const filterRoles = (currentRoles: string, allRoles: Role[]) => { let currentRolesString = currentRoles; if (currentRoles.includes("=")) { @@ -57,10 +83,10 @@ const filterRoles = (currentRoles: string, allRoles: { "metadata": { "name": str const filterable = currentRolesString.split(","); - return allRoles.filter((role: any) => { + return allRoles.filter((role: Role) => { return !filterable.includes(role.metadata.name); - }) -} + }); +}; const teleportRolesGenerator: Fig.Generator = { script: "tctl get roles --format json", @@ -70,80 +96,79 @@ const teleportRolesGenerator: Fig.Generator = { const filteredRoles = filterRoles(tokens[tokens.length - 1], roles); - return filteredRoles.map((role: any) => { + return filteredRoles.map((role: Role) => { return { name: role.metadata.name, - description: role.metadata.description - } - }) - } -} + description: role.metadata.description, + }; + }); + }, +}; const teleportAppGenerator: Fig.Generator = { script: "tsh apps ls --format json", postProcess: function (out) { const apps = JSON.parse(out); - return apps.map((app: any) => { + return apps.map((app: App) => { return { name: app.metadata.name, - description: app.metadata.description - } - }) - } -} - + description: app.metadata.description, + }; + }); + }, +}; const teleportDatabaseGenerator: Fig.Generator = { script: "tsh db ls --format json", postProcess: function (out) { const dbs = JSON.parse(out); - return dbs.map((db: any) => { + return dbs.map((db: Db) => { return { name: db.metadata.name, - description: db.metadata.description - } - }) - } -} + description: db.metadata.description, + }; + }); + }, +}; const teleportRequestGenerator: Fig.Generator = { script: "tctl request ls --format json", postProcess: function (out) { const requests = JSON.parse(out); - return requests.map((request: any) => { + return requests.map((request: AccessRequest) => { return { name: request.metadata.name, - description: request.metadata.description - } - }) - } -} + description: request.metadata.description, + }; + }); + }, +}; const teleportTokenGenerator: Fig.Generator = { script: "tctl tokens ls --format json", postProcess: function (out) { const tokens = JSON.parse(out); - return tokens.map((token: any) => { + return tokens.map((token: Token) => { return { name: token.metadata.name, - description: token.metadata.description - } - }) - } -} + description: token.metadata.description, + }; + }); + }, +}; const teleportWindowsDesktopGenerator: Fig.Generator = { script: "tctl get windows_desktop --format json", postProcess: function (out) { const desktops = JSON.parse(out); - return desktops.map((desktop: any) => { + return desktops.map((desktop: WindowsDesktop) => { return { name: desktop.metadata.name, - description: desktop.metadata.description - } - }) - } -} + description: desktop.metadata.description, + }; + }); + }, +}; const teleportFormatOption: Fig.Option = { name: "--format", @@ -151,108 +176,130 @@ const teleportFormatOption: Fig.Option = { args: { name: "format", suggestions: ["text", "json", "yaml"], - default: "text" - } -} + default: "text", + }, +}; const teleportBotsGenerator: Fig.Generator = { script: "tctl bots ls --format json", postProcess: function (out) { const bots = JSON.parse(out); - return bots.map((bot: any) => { + return bots.map((bot: Bot) => { return { name: bot.metadata.name.slice(4), - description: bot.metadata.description - } - }) - } -} + description: bot.metadata.description, + }; + }); + }, +}; const teleportACLGenerator: Fig.Generator = { script: "tctl acl ls --format json", postProcess: function (out) { const acl = JSON.parse(out); - return acl.map((acl: any) => { + return acl.map((acl: ACL) => { return { name: acl.metadata.name, - description: acl.metadata.description - } - }) - } -} + description: acl.metadata.description, + }; + }); + }, +}; const teleportAlertGenerator: Fig.Generator = { script: "tctl alerts list --format json", postProcess: function (out) { const alerts = JSON.parse(out); - return alerts.map((alert: any) => { + return alerts.map((alert: Alert) => { return { name: alert.metadata.name, - description: alert.metadata.description - } - }) - } -} + description: alert.metadata.description, + }; + }); + }, +}; const teleportDeviceGenerator: Fig.Generator = { script: "tctl get device --format json", postProcess: function (out) { const devices = JSON.parse(out); - return devices.map((device: any) => { + return devices.map((device: Device) => { return { name: device.metadata.name, - description: device.metadata.description - } - }) - } -} + description: device.metadata.description, + }; + }); + }, +}; const teleportSAMLConnectorGenerator: Fig.Generator = { script: "tctl get connector --format json", postProcess: function (out) { const connectors = JSON.parse(out); - return connectors.map((connector: any) => { + return connectors.map((connector: Connector) => { return { name: connector.metadata.name, - description: connector.metadata.description - } - }) - } -} + description: connector.metadata.description, + }; + }); + }, +}; const teleportGetResourcesGenerator: Fig.Generator = { trigger: (current, old) => { return true; }, custom: async (tokens, executeShellCommand) => { - const standardSuggestions = ["user", "role", "connector", "node", "windows_desktop", "cluster", "login_rule", "device", "ui_config", "cluster_auth_preference", "lock", "all"] - let respondSuggestions = standardSuggestions.map((suggestion) => { + const standardSuggestions = [ + "user", + "role", + "connector", + "node", + "windows_desktop", + "cluster", + "login_rule", + "device", + "ui_config", + "cluster_auth_preference", + "lock", + "all", + ]; + const respondSuggestions = standardSuggestions.map((suggestion) => { return { name: suggestion, - description: "Get a " + suggestion - } + description: "Get a " + suggestion, + }; }); - if (tokens.find((token) => standardSuggestions.includes(token.split("/")[0])) != undefined) { + if ( + tokens.find((token) => + standardSuggestions.includes(token.split("/")[0]) + ) != undefined + ) { // Get the name from the token which contains one of the standard suggestions - const resource = tokens.find((token) => standardSuggestions.includes(token.split("/")[0])).split("/")[0]; + const resource = tokens + .find((token) => standardSuggestions.includes(token.split("/")[0])) + .split("/")[0]; - if (standardSuggestions.find((sug) => sug === resource) == undefined) return respondSuggestions; + if (standardSuggestions.find((sug) => sug === resource) == undefined) + return respondSuggestions; if (["cluster_auth_preference", "all"].includes(resource)) return []; // This is what tctl expects - const resources = await executeShellCommand(`tctl get ${resource} --format json`); + const resources = await executeShellCommand( + `tctl get ${resource} --format json` + ); const parsedResources = JSON.parse(resources); - return parsedResources.map((parsedResource: any) => { + return parsedResources.map((parsedResource: GenericResource) => { return { - name: `${resource}/${parsedResource.metadata.name}` - } - }) + name: `${resource}/${parsedResource.metadata.name}`, + }; + }); } return respondSuggestions; - } -} + }, +}; /* tctl lock --help --user Name of a Teleport user to disable. @@ -275,188 +322,205 @@ const completionSpec: Fig.Spec = { /* tctl help */ { name: "help", - description: "Show help.", + description: "Show help", }, /* tctl users */ { - "name": "users", + name: "users", subcommands: [ { name: "add", - description: "Add a new user.", - args: [ - { - name: "account", - description: "Teleport user account name.", - } - ], + description: "Add a new user", + args: { + name: "account", + description: "Teleport user account name", + }, options: [ { name: "--logins", - description: "List of allowed SSH logins for the new user.", + description: "List of allowed SSH logins for the new user", }, { name: "--windows-logins", - description: "List of allowed Windows logins for the new user.", + description: "List of allowed Windows logins for the new user", }, { name: "--kubernetes-users", - description: "List of allowed Kubernetes users for the new user.", + description: "List of allowed Kubernetes users for the new user", }, { name: "--kubernetes-groups", - description: "List of allowed Kubernetes groups for the new user.", + description: "List of allowed Kubernetes groups for the new user", }, { name: "--db-users", - description: "List of allowed database users for the new user.", + description: "List of allowed database users for the new user", }, { name: "--db-names", - description: "List of allowed database names for the new user.", + description: "List of allowed database names for the new user", }, { name: "--db-roles", - description: "List of database roles for automatic database user provisioning." + description: + "List of database roles for automatic database user provisioning", }, { name: "--aws-role-arns", - description: "List of allowed AWS role ARNs for the new user.", + description: "List of allowed AWS role ARNs for the new user", }, { name: "--azure-identities", - description: "List of allowed Azure identities for the new user.", + description: "List of allowed Azure identities for the new user", }, { name: "--gcp-service-accounts", - description: "List of allowed GCP service accounts for the new user.", + description: + "List of allowed GCP service accounts for the new user", }, { name: "--host-user-uid", - description: "UID for auto provisioned host users to use.", + description: "UID for auto provisioned host users to use", }, { name: "--host-user-gid", - description: "GID for auto provisioned host users to use.", + description: "GID for auto provisioned host users to use", }, { name: "--ttl", - description: "Set expiration time for token, default is 1h0m0s, maximum is 48h0m0s.", + description: + "Set expiration time for token, default is 1h0m0s, maximum is 48h0m0s", }, { name: "--roles", - description: "List of roles for the new user to assume. Comma seperated.", + description: + "List of roles for the new user to assume. Comma seperated", isRequired: true, isRepeatable: true, args: { generators: teleportRolesGenerator, - } - } + }, + }, ], }, { name: "update", - description: "Update user account.", + description: "Update user account", options: [ { name: "--set-roles", - description: "List of roles for the user to assume, replaces current roles. Comma seperated.", + description: + "List of roles for the user to assume, replaces current roles. Comma seperated", args: { - generators: teleportRolesGenerator - } + generators: teleportRolesGenerator, + }, }, { name: "--set-logins", - description: "List of allowed SSH logins for the user, replaces current logins.", + description: + "List of allowed SSH logins for the user, replaces current logins", }, { name: "--set-windows-logins", - description: "List of allowed Windows logins for the user, replaces current Windows logins.", + description: + "List of allowed Windows logins for the user, replaces current Windows logins", }, { name: "--set-kubernetes-users", - description: "List of allowed Kubernetes users for the user, replaces current Kubernetes users.", + description: + "List of allowed Kubernetes users for the user, replaces current Kubernetes users", }, { name: "--set-kubernetes-groups", - description: "List of allowed Kubernetes groups for the user, replaces current Kubernetes groups.", + description: + "List of allowed Kubernetes groups for the user, replaces current Kubernetes groups", }, { name: "--set-db-users", - description: "List of allowed database users for the user, replaces current database users.", + description: + "List of allowed database users for the user, replaces current database users", }, { name: "--set-db-names", - description: "List of allowed database names for the user, replaces current database names.", + description: + "List of allowed database names for the user, replaces current database names", }, { name: "--set-db-roles", - description: "List of allowed database roles for automatic database user provisioning, replaces current database roles.", + description: + "List of allowed database roles for automatic database user provisioning, replaces current database roles", }, { name: "--set-aws-role-arns", - description: "List of allowed AWS role ARNs for the user, replaces current AWS role ARNs.", + description: + "List of allowed AWS role ARNs for the user, replaces current AWS role ARNs", }, { name: "--set-azure-identities", - description: "List of allowed Azure identities for the user, replaces current Azure identities.", + description: + "List of allowed Azure identities for the user, replaces current Azure identities", }, { name: "--set-gcp-service-accounts", - description: "List of allowed GCP service accounts for the user, replaces current service accounts.", + description: + "List of allowed GCP service accounts for the user, replaces current service accounts", }, { name: "--set-host-user-uid", - description: "UID for auto provisioned host users to use. Value can be reset by providing an empty string.", + description: + "UID for auto provisioned host users to use. Value can be reset by providing an empty string", }, { name: "--set-host-user-gid", - description: "GID for auto provisioned host users to use. Value can be reset by providing an empty string.", - } + description: + "GID for auto provisioned host users to use. Value can be reset by providing an empty string", + }, ], args: { name: "account", - description: "Teleport user account name.", - generators: teleportAccountsGenerator - } + description: "Teleport user account name", + generators: teleportAccountsGenerator, + }, }, { name: "ls", - description: "Lists all user accounts.", + description: "Lists all user accounts", }, { name: "rm", - description: "Remove user account.", + description: "Remove user account", args: { name: "account", - description: "Teleport user account name.", + description: "Teleport user account name", isVariadic: true, - generators: teleportAccountsGenerator - } + generators: teleportAccountsGenerator, + }, }, { name: "reset", - description: "Reset user password and generate a new token [Teleport DB users only].", + description: + "Reset user password and generate a new token [Teleport DB users only]", args: { name: "account", - description: "Teleport user account name.", - generators: teleportAccountsGenerator - } - } - ] + description: "Teleport user account name", + generators: teleportAccountsGenerator, + }, + }, + ], }, /* tctl nodes */ { name: "nodes", - description: "Issue invites for other nodes to join the cluster.", + description: "Issue invites for other nodes to join the cluster", subcommands: [ { name: "add", - description: "Generate a node invitation token.", + description: "Generate a node invitation token", options: [ { name: "--roles", - description: "Comma-separated list of roles for the new node to assume", + description: + "Comma-separated list of roles for the new node to assume", args: { generators: { getQueryTerm: commaQueryTerm, @@ -464,37 +528,40 @@ const completionSpec: Fig.Spec = { return true; }, custom: async (tokens) => { - const roles = ["node", "proxy", "auth", "app", "db"] + const roles = ["node", "proxy", "auth", "app", "db"]; - const filteredRoles = filterRoles(tokens[tokens.length - 1], roles.map((role) => { - return { - "metadata": { - "name": role, - "description": "Add a new " + role + " to the cluster" - } - } - })); - - console.log(filterRoles) + const filteredRoles = filterRoles( + tokens[tokens.length - 1], + roles.map((role) => { + return { + metadata: { + name: role, + description: + "Add a new " + role + " to the cluster", + }, + }; + }) + ); - return filteredRoles.map((role: any) => { + return filteredRoles.map((role: Role) => { return { name: role.metadata.name, - } - }) - } - } - } + }; + }); + }, + }, + }, }, { name: "--ttl", - description: "Time to live for a generated token, default is 0h30m0s, maximum is 48h0m0s.", - } - ] + description: + "Time to live for a generated token, default is 0h30m0s, maximum is 48h0m0s", + }, + ], }, { name: "ls", - description: "List all active SSH nodes within the cluster.", + description: "List all active SSH nodes within the cluster", options: [ { name: "--namespace", @@ -507,28 +574,29 @@ const completionSpec: Fig.Spec = { script: "tctl get namespaces --format json", postProcess: function (out) { const namespaces = JSON.parse(out); - return namespaces.map((namespace: any) => { + + return namespaces.map((namespace: Namespace) => { return { name: namespace.metadata.name, - description: namespace.metadata.description - } - }) - } - } - } - } - ] - } - ] + description: namespace.metadata.description, + }; + }); + }, + }, + }, + }, + ], + }, + ], }, /* tctl tokens */ { name: "tokens", - description: "Manage invitation tokens.", + description: "Manage invitation tokens", subcommands: [ { name: "add", - description: "Create a invitation token.", + description: "Create a invitation token", args: {}, options: [ teleportFormatOption, @@ -539,165 +607,192 @@ const completionSpec: Fig.Spec = { requiresSeparator: true, args: { name: "type", - description: "node,app,db,proxy,etc", + description: "Node,app,db,proxy,etc", default: "app", - isOptional: false, + generators: { getQueryTerm: commaQueryTerm, custom: async (tokens) => { const types = [ { - "metadata": { - "name": "proxy", - "description": "Add a new proxy node to the cluster" - } + metadata: { + name: "proxy", + description: "Add a new proxy node to the cluster", + }, }, { - "metadata": { - "name": "auth", - "description": "Add a new auth service node to the cluster" - } + metadata: { + name: "auth", + description: + "Add a new auth service node to the cluster", + }, }, { - "metadata": { - "name": "trusted_cluster", - "description": "Add a new trusted cluster to the cluster" - } + metadata: { + name: "trusted_cluster", + description: + "Add a new trusted cluster to the cluster", + }, }, { - "metadata": { - "name": "node", - "description": "Aadd a new (ssh-)node to the cluster" - } + metadata: { + name: "node", + description: "Aadd a new (ssh-)node to the cluster", + }, }, { - "metadata": { - "name": "db", - "description": "Add a new database to the cluster" - } + metadata: { + name: "db", + description: "Add a new database to the cluster", + }, }, { - "metadata": { - "name": "kube", - "description": "Add a new kubernetes cluster to the cluster" - } + metadata: { + name: "kube", + description: + "Add a new kubernetes cluster to the cluster", + }, }, { - "metadata": { - "name": "app", - "description": "Add a new (web-)application to the cluster" - } + metadata: { + name: "app", + description: + "Add a new (web-)application to the cluster", + }, }, { - "metadata": { - "name": "windowsdesktop", - "description": "Add a new windows desktop to the cluster" - } - } - ] + metadata: { + name: "windowsdesktop", + description: + "Add a new windows desktop to the cluster", + }, + }, + ]; - const filteredTypes = filterRoles(tokens[tokens.length - 1], types); + const filteredTypes = filterRoles( + tokens[tokens.length - 1], + types + ); return filteredTypes.map((type) => { return { name: type.metadata.name, - description: type.metadata.description - } - }) - } - } - } + description: type.metadata.description, + }; + }); + }, + }, + }, }, { name: "--value", - description: "Override the default random generated token with a specified value", + description: + "Override the default random generated token with a specified value", args: { name: "value", - } + }, }, { name: "--labels", description: "Set token labels", args: { name: "label1=value1,label2=value2", - } + }, }, { name: "--ttl", - description: "Set expiration time for token, default is 30 minutes", + description: + "Set expiration time for token, default is 30 minutes", args: { name: "30m", - } + }, }, { name: "--app-name", description: "Name of the application to add", args: { name: "name", - } + }, }, { name: "--app-uri", description: "URI of the application to add", args: { name: "app.example.com", - } + }, }, { name: "--db-name", description: "Name of the database to add", args: { name: "name", - } + }, }, { name: "--db-protocol", - description: "Database protocol to use. Supported are: [postgres mysql mongodb oracle cockroachdb redis snowflake sqlserver cassandra elasticsearch opensearch dynamodb clickhouse clickhouse-http]", + description: + "Database protocol to use. Supported are: [postgres mysql mongodb oracle cockroachdb redis snowflake sqlserver cassandra elasticsearch opensearch dynamodb clickhouse clickhouse-http]", args: { name: "protocol", - suggestions: ["postgres", "mysql", "mongodb", "oracle", "cockroachdb", "redis", "snowflake", "sqlserver", "cassandra", "elasticsearch", "opensearch", "dynamodb", "clickhouse", "clickhouse-http"], + suggestions: [ + "postgres", + "mysql", + "mongodb", + "oracle", + "cockroachdb", + "redis", + "snowflake", + "sqlserver", + "cassandra", + "elasticsearch", + "opensearch", + "dynamodb", + "clickhouse", + "clickhouse-http", + ], default: "postgres", - } - } - ] + }, + }, + ], }, { name: ["rm", "del"], - description: "Delete/revoke an invitation token.", + description: "Delete/revoke an invitation token", args: { name: "token", - description: "Token to delete.", + description: "Token to delete", isVariadic: true, generators: { script: "tctl get tokens --format json", postProcess: function (out) { const tokens = JSON.parse(out); - return tokens.map((token: any) => { + return tokens.map((token: Token) => { return { name: token.metadata.name, - description: token.metadata.description - } - }) - } - } - } + description: token.metadata.description, + }; + }); + }, + }, + }, }, { name: "ls", - description: "List node and user invitation tokens.", - options: [teleportFormatOption] - } - ] + description: "List node and user invitation tokens", + options: [teleportFormatOption], + }, + ], }, /* tctl auth */ { name: "auth", - description: "Operations with user and host certificate authorities (CAs).", + description: + "Operations with user and host certificate authorities (CAs)", args: {}, subcommands: [ { name: "export", - description: "Export public cluster (CA) keys to stdout.", + description: "Export public cluster (CA) keys to stdout", options: [ { name: "--keys", @@ -705,30 +800,42 @@ const completionSpec: Fig.Spec = { }, { name: "--fingerprint", - description: "Filter authority by fingerprint" + description: "Filter authority by fingerprint", }, { name: "--compat", - description: "Export certificates compatible with specific version of Teleport", + description: + "Export certificates compatible with specific version of Teleport", args: { name: "version", - description: "E.g. 13" - } + description: "E.g. 13", + }, }, { name: "--type", description: "Export certificate type", args: { name: "type", - suggestions: ["user", "host", "tls-host", "tls-user", "tls-user-der", "windows", "db", "db-der", "openssh", "saml-idp"], + suggestions: [ + "user", + "host", + "tls-host", + "tls-user", + "tls-user-der", + "windows", + "db", + "db-der", + "openssh", + "saml-idp", + ], description: "Export certificate type", - } - } - ] + }, + }, + ], }, { name: "sign", - description: "Create an identity file(s) for a given user.", + description: "Create an identity file(s) for a given user", args: {}, options: [ { @@ -737,15 +844,15 @@ const completionSpec: Fig.Spec = { isRequired: true, args: { name: "user", - generators: teleportAccountsGenerator - } + generators: teleportAccountsGenerator, + }, }, { name: "--host", description: "Teleport host name", args: { name: "host", - } + }, }, { name: ["--out", "-o"], @@ -753,8 +860,8 @@ const completionSpec: Fig.Spec = { isRequired: true, args: { name: "out", - template: "filepaths" - } + template: "filepaths", + }, }, { name: "--format", @@ -763,14 +870,14 @@ const completionSpec: Fig.Spec = { name: "format", suggestions: ["file", "openssh", "tls", "kubernetes"], default: "file", - } + }, }, { name: "--ttl", - description: "TTL (time to live) for the generated certificate.", + description: "TTL (time to live) for the generated certificate", args: { name: "ttl", - } + }, }, { name: "--compat", @@ -778,91 +885,106 @@ const completionSpec: Fig.Spec = { }, { name: "--proxy", - description: "Address of the Teleport proxy. When --format is set to \"kubernetes\", this address will be set as cluster address in the generated kubeconfig file", + description: + 'Address of the Teleport proxy. When --format is set to "kubernetes", this address will be set as cluster address in the generated kubeconfig file', args: { name: "proxy", - } + }, }, { name: "--overwrite", - description: "Whether to overwrite existing destination files. When not set, user will be prompted before overwriting any existing file.", + description: + "Whether to overwrite existing destination files. When not set, user will be prompted before overwriting any existing file", }, { name: "--no-overwrite", - description: "Whether to overwrite existing destination files. When not set, user will be prompted before overwriting any existing file.", + description: + "Whether to overwrite existing destination files. When not set, user will be prompted before overwriting any existing file", }, { name: "--tar", - description: "Create a tarball of the resulting certificates and stream to stdout.", + description: + "Create a tarball of the resulting certificates and stream to stdout", }, { name: "--leaf-cluster", - description: "Leaf cluster to generate identity file for when --format is set to \"kubernetes\"", + description: + 'Leaf cluster to generate identity file for when --format is set to "kubernetes"', args: { name: "leaf-cluster", - } + }, }, { name: "--kube-cluster-name", - description: "Kubernetes cluster to generate identity file for when --format is set to \"kubernetes\"", + description: + 'Kubernetes cluster to generate identity file for when --format is set to "kubernetes"', args: { name: "name", - generators: teleportKubernetesClustersGenerator - } + generators: teleportKubernetesClustersGenerator, + }, }, { name: "--app-name", - description: "Application to generate identity file for. Mutually exclusive with \"--db-service\".", + description: + 'Application to generate identity file for. Mutually exclusive with "--db-service"', args: { name: "name", - generators: teleportAppGenerator - } + generators: teleportAppGenerator, + }, }, { name: "--db-service", - description: "Database to generate identity file for. Mutually exclusive with \"--app-name\".", + description: + 'Database to generate identity file for. Mutually exclusive with "--app-name"', args: { name: "service", - generators: teleportDatabaseGenerator - } + generators: teleportDatabaseGenerator, + }, }, { name: "--db-user", - description: "Database user placed on the identity file. Only used when \"--db-service\" is set.", + description: + 'Database user placed on the identity file. Only used when "--db-service" is set', }, { name: "--db-name", - description: "Database name placed on the identity file. Only used when \"--db-service\" is set.", + description: + 'Database name placed on the identity file. Only used when "--db-service" is set', }, { name: "--windows-user", - description: "Window user placed on the identity file. Only used when --format is set to \"windows\"", + description: + 'Window user placed on the identity file. Only used when --format is set to "windows"', }, { name: "--windows-domain", - description: "Active Directory domain for which this cert is valid. Only used when --format is set to \"windows\"", + description: + 'Active Directory domain for which this cert is valid. Only used when --format is set to "windows"', }, { name: "--windows-sid", - description: "Optional Security Identifier to embed in the certificate. Only used when --format is set to \"windows\"", - } - ] + description: + 'Optional Security Identifier to embed in the certificate. Only used when --format is set to "windows"', + }, + ], }, { name: "rotate", - description: "Rotate certificate authorities in the cluster.", + description: "Rotate certificate authorities in the cluster", options: [ { name: "--grace-period", - description: "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to re-login and nodes to re-register.", + description: + "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to re-login and nodes to re-register", args: { name: "duration", - description: "relative duration like 5s, 2m, or 3h" - } + description: "Relative duration like 5s, 2m, or 3h", + }, }, { name: "--manual", - description: "Activate manual rotation, set rotation phases manually", + description: + "Activate manual rotation, set rotation phases manually", }, { name: "--type", @@ -870,29 +992,45 @@ const completionSpec: Fig.Spec = { isRequired: true, args: { name: "type", - suggestions: ["host", "user", "db", "openssh", "jwt", "saml_idp", "oidc_idp"], + suggestions: [ + "host", + "user", + "db", + "openssh", + "jwt", + "saml_idp", + "oidc_idp", + ], default: "host", - } + }, }, { name: "--phase", - description: "Target rotation phase to set, used in manual rotation", + description: + "Target rotation phase to set, used in manual rotation", args: { name: "phase", - suggestions: ["init", "standby", "update_clients", "update_servers", "rollback"], + suggestions: [ + "init", + "standby", + "update_clients", + "update_servers", + "rollback", + ], default: "init", - } - } - ] + }, + }, + ], }, { name: "ls", - description: "List connected auth servers.", - options: [teleportFormatOption] + description: "List connected auth servers", + options: [teleportFormatOption], }, { name: "crl", - description: "Export empty certificate revocation list (CRL) for certificate authorities.", + description: + "Export empty certificate revocation list (CRL) for certificate authorities", options: [ { name: "--type", @@ -900,44 +1038,45 @@ const completionSpec: Fig.Spec = { isRequired: true, args: { name: "type", - suggestions: ["host", "db",], + suggestions: ["host", "db"], default: "host", - } - } - ] - } - ] + }, + }, + ], + }, + ], }, /* tctl get */ { name: "get", - description: "Get a resource.", + description: "Get a resource", args: { name: "resource", description: "Resource to get", - generators: teleportGetResourcesGenerator + generators: teleportGetResourcesGenerator, }, options: [ teleportFormatOption, { name: "--with-secrets", - description: "Include secrets in resources like certificate authorities or OIDC connectors", + description: + "Include secrets in resources like certificate authorities or OIDC connectors", }, { name: "--verbose", description: "Verbose table output, shows full label output", - } - ] + }, + ], }, /* tctl status */ { name: "status", - description: "Report cluster status.", + description: "Report cluster status", }, /* tctl top */ { name: "top", - description: "Report cluster status.", + description: "Report cluster status", args: [ { name: "diag-address", @@ -945,54 +1084,54 @@ const completionSpec: Fig.Spec = { }, { name: "refresh", - description: "Refresh period" - } - ] + description: "Refresh period", + }, + ], }, /* tctl requests */ { name: ["requests", "request"], - description: "Manage access requests.", + description: "Manage access requests", args: {}, subcommands: [ { name: "ls", - description: "Show active access requests." + description: "Show active access requests", }, { name: "get", - description: "Show access request details.", + description: "Show access request details", args: { name: "request", description: "Access request ID", - generators: teleportRequestGenerator - } + generators: teleportRequestGenerator, + }, }, { name: "approve", - description: "Approve pending access request.", + description: "Approve pending access request", args: { name: "request", description: "Access request ID", - generators: teleportRequestGenerator - } + generators: teleportRequestGenerator, + }, }, { name: "deny", - description: "Deny pending access request.", + description: "Deny pending access request", args: { name: "request", description: "Access request ID", - generators: teleportRequestGenerator - } + generators: teleportRequestGenerator, + }, }, { name: "create", - description: "Create pending access request.", + description: "Create pending access request", args: { name: "username", description: "Name of target user", - generators: teleportAccountsGenerator + generators: teleportAccountsGenerator, }, options: [ { @@ -1000,8 +1139,8 @@ const completionSpec: Fig.Spec = { description: "Roles to be requested", args: { name: "roles", - generators: teleportRolesGenerator - } + generators: teleportRolesGenerator, + }, }, { name: "--reason", @@ -1012,33 +1151,33 @@ const completionSpec: Fig.Spec = { description: "Resource ID to be requested", args: { name: "resource", - generators: teleportGetResourcesGenerator - } + generators: teleportGetResourcesGenerator, + }, }, { name: "--dry-run", - description: "Don't actually generate the access request" - } - ] + description: "Don't actually generate the access request", + }, + ], }, { name: "rm", - description: "Delete an access request.", + description: "Delete an access request", options: [ { name: ["--force", "-f"], - description: "Force the deletion of an active access request" - } + description: "Force the deletion of an active access request", + }, ], args: { name: "request-id", description: "Access request ID", - generators: teleportRequestGenerator - } + generators: teleportRequestGenerator, + }, }, { name: "review", - description: "Review an access request.", + description: "Review an access request", options: [ { name: "--author", @@ -1046,277 +1185,281 @@ const completionSpec: Fig.Spec = { isRequired: true, args: { name: "author", - generators: teleportAccountsGenerator - } + generators: teleportAccountsGenerator, + }, }, { name: "--approve", - description: "Review proposes approval" + description: "Review proposes approval", }, { name: "--deny", - description: "Review proposes denial" + description: "Review proposes denial", }, ], args: { name: "request-id", description: "Access request ID", - generators: teleportRequestGenerator - } - } - ] + generators: teleportRequestGenerator, + }, + }, + ], }, /* tctl apps */ { name: "apps", - description: "Operate on applications registered with the cluster.", + description: "Operate on applications registered with the cluster", args: {}, requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List all applications registered with the cluster.", - } - ] + description: "List all applications registered with the cluster", + }, + ], }, /* tctl db */ { name: "db", - description: "Operate on databases registered with the cluster.", + description: "Operate on databases registered with the cluster", args: {}, requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List all databases registered with the cluster.", - } - ] + description: "List all databases registered with the cluster", + }, + ], }, /* tctl kube */ { name: "kube", - description: "Operate on registered Kubernetes clusters.", + description: "Operate on registered Kubernetes clusters", args: {}, requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List all Kubernetes clusters registered with the cluster.", - } - ] + description: + "List all Kubernetes clusters registered with the cluster", + }, + ], }, /* tctl windows_desktops */ { name: "windows_desktops", - description: "Operate on registered Windows desktops.", + description: "Operate on registered Windows desktops", args: {}, requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List all Windows desktops registered with the cluster.", - } - ] + description: "List all Windows desktops registered with the cluster", + }, + ], }, /* tctl proxy */ { name: "proxy", - description: "Operations with information for cluster proxies.", + description: "Operations with information for cluster proxies", args: {}, requiresSubcommand: true, subcommands: [ { name: "ls", - description: "Lists proxies connected to the cluster.", - options: [teleportFormatOption] - } - ] + description: "Lists proxies connected to the cluster", + options: [teleportFormatOption], + }, + ], }, /* tctl rm */ { name: ["rm", "del"], - description: "Delete a resource.", + description: "Delete a resource", args: { name: "resource type/resource name", description: "Resource to delete", - generators: teleportGetResourcesGenerator + generators: teleportGetResourcesGenerator, }, }, /* tctl lock */ { name: "lock", - description: "Create a new lock.", + description: "Create a new lock", args: {}, options: [ { name: "--user", - description: "Name of a Teleport user to disable.", + description: "Name of a Teleport user to disable", args: { name: "user", - generators: teleportAccountsGenerator - } + generators: teleportAccountsGenerator, + }, }, { name: "--role", - description: "Name of a Teleport role to disable.", + description: "Name of a Teleport role to disable", args: { name: "role", - generators: teleportRolesGenerator - } + generators: teleportRolesGenerator, + }, }, { name: "--login", - description: "Name of a local UNIX user to disable.", + description: "Name of a local UNIX user to disable", }, { name: "--mfa-device", - description: "UUID of a user MFA device to disable.", + description: "UUID of a user MFA device to disable", }, { name: "--windows-desktop", - description: "Name of a Windows desktop to disable.", + description: "Name of a Windows desktop to disable", args: { name: "desktop", - generators: teleportWindowsDesktopGenerator - } + generators: teleportWindowsDesktopGenerator, + }, }, { name: "--access-request", - description: "UUID of an access request to disable.", + description: "UUID of an access request to disable", args: { name: "request", - generators: teleportRequestGenerator - } + generators: teleportRequestGenerator, + }, }, { name: "--device", - description: "UUID of a trusted device to disable.", + description: "UUID of a trusted device to disable", }, { name: "--message", - description: "Message to display to locked-out users.", + description: "Message to display to locked-out users", }, { name: "--expires", - description: "Time point (RFC3339) when the lock expires.", + description: "Time point (RFC3339) when the lock expires", args: { name: "yyyy-MM-ddThh:mm:ss.msZ", - description: "Time point (RFC3339) when the lock expires.", - } + description: "Time point (RFC3339) when the lock expires", + }, }, { name: "--ttl", - description: "Time duration after which the lock expires.", + description: "Time duration after which the lock expires", args: { name: "duration", - description: "Time duration after which the lock expires.", - } + description: "Time duration after which the lock expires", + }, }, { name: "--server-id", - description: "UUID of a Teleport server to disable.", - } - ] + description: "UUID of a Teleport server to disable", + }, + ], }, /* tctl bots */ { name: "bots", - description: "Operate on certificate renewal bots registered with the cluster.", + description: + "Operate on certificate renewal bots registered with the cluster", requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List all certificate renewal bots registered with the cluster.", + description: + "List all certificate renewal bots registered with the cluster", }, { name: "add", - description: "Add a new certificate renewal bot to the cluster.", + description: "Add a new certificate renewal bot to the cluster", args: { name: "name", - description: "A name to uniquely identify this bot in the cluster.", + description: "A name to uniquely identify this bot in the cluster", }, options: [ { name: "--roles", - description: "Roles the bot is able to assume.", + description: "Roles the bot is able to assume", isRequired: true, args: { name: "roles", - generators: teleportRolesGenerator - } + generators: teleportRolesGenerator, + }, }, { name: "--ttl", - description: "TTL for the bot join token.", + description: "TTL for the bot join token", }, { name: "--token", - description: "Name of an existing token to use.", + description: "Name of an existing token to use", args: { name: "token", - generators: teleportTokenGenerator - } + generators: teleportTokenGenerator, + }, }, { name: "--logins", description: "List of allowed SSH logins for the bot user", - } - ] + }, + ], }, { name: "rm", - description: "Permanently remove a certificate renewal bot from the cluster.", + description: + "Permanently remove a certificate renewal bot from the cluster", args: { name: "name", - description: "Name of an existing bot to remove.", - generators: teleportBotsGenerator - } - } - ] + description: "Name of an existing bot to remove", + generators: teleportBotsGenerator, + }, + }, + ], }, /* tctl inventory */ { name: "inventory", - description: "Manage Teleport instance inventory.", + description: "Manage Teleport instance inventory", requiresSubcommand: true, subcommands: [ { name: "status", - description: "Show inventory status summary.", + description: "Show inventory status summary", options: [ { name: "--connected", - description: "Show locally connected instances summary" - } - ] + description: "Show locally connected instances summary", + }, + ], }, { name: ["list", "ls"], - description: "List Teleport instance inventory.", + description: "List Teleport instance inventory", options: [ { name: "--older-than", description: "Filter for older teleport versions", args: { name: "version", - description: "E.g. 13" - } + description: "E.g. 13", + }, }, { name: "--newer-than", description: "Filter for newer teleport versions", args: { name: "version", - description: "E.g. 13" - } + description: "E.g. 13", + }, }, { name: "--exact-version", description: "Filter for exact teleport version", args: { name: "version", - description: "E.g. 13" - } + description: "E.g. 13", + }, }, { name: "--services", @@ -1324,8 +1467,16 @@ const completionSpec: Fig.Spec = { args: { name: "service", description: "E.g. node", - suggestions: ["node", "kube", "proxy", "auth", "app", "db", "windowsdesktop"] - } + suggestions: [ + "node", + "kube", + "proxy", + "auth", + "app", + "db", + "windowsdesktop", + ], + }, }, { name: "--upgrader", @@ -1333,34 +1484,34 @@ const completionSpec: Fig.Spec = { args: { name: "upgrader", description: "E.g. kube", - suggestions: ["kube", "unit", "none"] - } - } - ] - } - ] + suggestions: ["kube", "unit", "none"], + }, + }, + ], + }, + ], }, /* tctl recordings */ { name: "recordings", - description: "View and control session recordings.", + description: "View and control session recordings", requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List recorded sessions.", - } - ] + description: "List recorded sessions", + }, + ], }, /* tctl alerts */ { name: "alerts", - description: "Manage cluster alerts.", + description: "Manage cluster alerts", requiresSubcommand: true, subcommands: [ { name: "list", - description: "List cluster alerts.", + description: "List cluster alerts", options: [ teleportFormatOption, { @@ -1368,78 +1519,81 @@ const completionSpec: Fig.Spec = { description: "List of comma separated labels to filter by labels", args: { name: "label1=value1,label2=value2", - } + }, }, { name: ["--verbose", "-v"], - description: "Show detailed alert info, including acknowledged alerts." - } - ] + description: + "Show detailed alert info, including acknowledged alerts", + }, + ], }, { name: "create", - description: "Create cluster alerts.", + description: "Create cluster alerts", options: [ { name: "--labels", description: "List of comma separated labels to filter by labels", args: { name: "label1=value1,label2=value2", - } + }, }, { name: "--severity", - description: "Severity of the alert (low, medium, or high).", + description: "Severity of the alert (low, medium, or high)", args: { name: "severity", - suggestions: ["low", "medium", "high"] - } + suggestions: ["low", "medium", "high"], + }, }, { name: "--ttl", - description: "Time duration after which the alert expires (default 24h).", - } + description: + "Time duration after which the alert expires (default 24h)", + }, ], args: { name: "message", description: "Alert body message", - } + }, }, { name: "ack", - description: "Acknowledge cluster alerts.", + description: "Acknowledge cluster alerts", subcommands: [ { name: "ls", - description: "List acknowledged cluster alerts.", - } + description: "List acknowledged cluster alerts", + }, ], args: { name: "id", - description: "The cluster alert ID.", - generators: teleportAlertGenerator + description: "The cluster alert ID", + generators: teleportAlertGenerator, }, options: [ { name: "--reason", - description: "The reason for acknowledging the cluster alert.", + description: "The reason for acknowledging the cluster alert", }, { name: "--ttl", - description: "Time duration after which the alert expires (default 24h).", + description: + "Time duration after which the alert expires (default 24h)", }, { name: "--clear", - description: "Clear the acknowledgment for the cluster alert." - } - ] + description: "Clear the acknowledgment for the cluster alert", + }, + ], }, - ] + ], }, /* tctl create */ { name: "create", - description: "Create or update a Teleport resource from a YAML file.", + description: "Create or update a Teleport resource from a YAML file", args: { name: "filename", template: "filepaths", @@ -1447,18 +1601,18 @@ const completionSpec: Fig.Spec = { options: [ { name: ["--force", "-f"], - description: "Overwrite the resource if already exists." - } - ] + description: "Overwrite the resource if already exists", + }, + ], }, /* tctl update */ { name: "update", - description: "Update resource fields.", + description: "Update resource fields", args: { name: "resource type/resource name", description: "Resource to update", - generators: teleportGetResourcesGenerator + generators: teleportGetResourcesGenerator, }, options: [ { @@ -1468,190 +1622,196 @@ const completionSpec: Fig.Spec = { { name: "--set-ttl", description: "Set TTL", - } - ] + }, + ], }, /* tctl edit */ { name: "edit", - description: "Edit a Teleport resource.", + description: "Edit a Teleport resource", args: { name: "resource type/resource name", description: "Resource to edit", - generators: teleportGetResourcesGenerator + generators: teleportGetResourcesGenerator, }, }, /* tctl devices */ { name: "devices", - description: "Register and manage trusted devices.", + description: "Register and manage trusted devices", requiresSubcommand: true, subcommands: [ { name: "add", - description: "Register managed devices.", + description: "Register managed devices", options: [ { name: "--os", description: "Operating System", - exclusiveOn: ["--current-device"] + exclusiveOn: ["--current-device"], }, { name: "--asset-tag", - description: "Inventory identifier for the device (e.g., Mac serial number)", - exclusiveOn: ["--current-device"] + description: + "Inventory identifier for the device (e.g., Mac serial number)", + exclusiveOn: ["--current-device"], }, { name: "--current-device", - description: "Registers the current device. Overrides --os and --asset-tag.", - exclusiveOn: ["--os", "--asset-tag"] + description: + "Registers the current device. Overrides --os and --asset-tag", + exclusiveOn: ["--os", "--asset-tag"], }, { name: "--enroll", - description: "If set, creates a device enrollment token.", + description: "If set, creates a device enrollment token", }, { name: "--enroll-ttl", description: "Time duration for the enrollment token", dependsOn: ["--enroll"], - } - ] + }, + ], }, { name: "ls", - description: "Lists managed devices." + description: "Lists managed devices", }, { name: "rm", - description: "Removes a managed device.", + description: "Removes a managed device", args: { name: "device", description: "Device ID", - generators: teleportDeviceGenerator - } + generators: teleportDeviceGenerator, + }, }, { name: "enroll", - description: "Creates a new device enrollment token.", + description: "Creates a new device enrollment token", options: [ { name: "--asset-tag", - description: "Inventory identifier for the device (e.g., Mac serial number)", - exclusiveOn: ["--current-device"] + description: + "Inventory identifier for the device (e.g., Mac serial number)", + exclusiveOn: ["--current-device"], }, { name: "--current-device", - description: "Registers the current device. Overrides --os and --asset-tag.", - exclusiveOn: ["--os", "--asset-tag"] + description: + "Registers the current device. Overrides --os and --asset-tag", + exclusiveOn: ["--os", "--asset-tag"], }, - ] + ], }, { name: "lock", - description: "Locks a device.", + description: "Locks a device", args: { name: "device", description: "Device ID", - generators: teleportDeviceGenerator - } - } - ] + generators: teleportDeviceGenerator, + }, + }, + ], }, /* tctl saml */ { name: "saml", - description: "Operations on SAML auth connectors.", + description: "Operations on SAML auth connectors", requiresSubcommand: true, subcommands: [ { name: "export", - description: "Export a SAML signing key in .crt format.", + description: "Export a SAML signing key in .crt format", args: { name: "connector_name", description: "Name of the SAML connector to export the key from", generators: teleportSAMLConnectorGenerator, - } - } - ] + }, + }, + ], }, /* tctl acl */ { name: ["acl", "access-lists"], - description: "Manage access lists.", + description: "Manage access lists", requiresSubcommand: true, subcommands: [ { name: "ls", - description: "List cluster access lists.", + description: "List cluster access lists", }, { name: "get", - description: "Get detailed information for an access list.", + description: "Get detailed information for an access list", args: { name: "access list", description: "Access list name", - generators: teleportACLGenerator - } + generators: teleportACLGenerator, + }, }, { name: "users", - description: "Manage user membership to access lists.", + description: "Manage user membership to access lists", requiresSubcommand: true, subcommands: [ { name: "add", - description: "Add a user to an access list.", + description: "Add a user to an access list", args: [ { name: "access-list-name", description: "The access list name", - generators: teleportACLGenerator + generators: teleportACLGenerator, }, { name: "user", description: "The user name", - generators: teleportAccountsGenerator + generators: teleportAccountsGenerator, }, { name: "expires", - description: "When the user's access expires (must be in RFC3339). Defaults to the expiration time of the access list.", + description: + "When the user's access expires (must be in RFC3339). Defaults to the expiration time of the access list", isOptional: true, }, { name: "reason", - description: "The reason the user has been added to the access list. Defaults to empty.", + description: + "The reason the user has been added to the access list. Defaults to empty", isOptional: true, - } - ] + }, + ], }, { name: "rm", - description: "Remove a user from an access list.", + description: "Remove a user from an access list", args: [ { name: "access-list-name", description: "The access list name", - generators: teleportACLGenerator + generators: teleportACLGenerator, }, { name: "user", description: "The user name", - generators: teleportAccountsGenerator - } - ] + generators: teleportAccountsGenerator, + }, + ], }, { name: "ls", - description: "List users that are members of an access list.", + description: "List users that are members of an access list", args: { name: "access list", description: "Access list name", - generators: teleportACLGenerator - } - } - ] - } - ] + generators: teleportACLGenerator, + }, + }, + ], + }, + ], }, /* tctl login_rule */ { @@ -1661,39 +1821,41 @@ const completionSpec: Fig.Spec = { subcommands: [ { name: "test", - description: "Test the parsing and evaluation of login rules.", - } - ] + description: "Test the parsing and evaluation of login rules", + }, + ], }, /* tctl sso */ { name: "sso", - description: "A family of commands for configuring and testing auth connectors (SSO).", + description: + "A family of commands for configuring and testing auth connectors (SSO)", requiresSubcommand: true, subcommands: [ { name: "configure", - description: "Create auth connector configuration.", + description: "Create auth connector configuration", requiresSubcommand: true, subcommands: [ { name: "github", - description: "Configure GitHub auth connector.", + description: "Configure GitHub auth connector", options: [ { name: "--name", - description: "Connector name.", + description: "Connector name", }, { name: "--teams-to-roles", - description: "Sets teams-to-roles mapping using format 'organization,name,role1,role2,...'. Repeatable.", + description: + "Sets teams-to-roles mapping using format 'organization,name,role1,role2,...'. Repeatable", args: { name: "teams-to-roles", - } + }, }, { name: "--display", - description: "Sets the connector display name.", + description: "Sets the connector display name", }, { name: "--id", @@ -1705,47 +1867,50 @@ const completionSpec: Fig.Spec = { }, { name: "--endpoint-url", - description: "Endpoint URL for GitHub instance.", + description: "Endpoint URL for GitHub instance", }, { name: "--api-endpoint-url", - description: "API endpoint URL for GitHub instance.", + description: "API endpoint URL for GitHub instance", }, { name: "--redirect-url", - description: "Authorization callback URL.", + description: "Authorization callback URL", }, { name: "--ignore-missing-roles", - description: "Ignore missing roles referenced in --teams-to-roles.", + description: + "Ignore missing roles referenced in --teams-to-roles", dependsOn: ["--teams-to-roles"], - } - ] - } - ] + }, + ], + }, + ], }, { name: "test", - description: "Perform end-to-end test of SSO flow using provided auth connector definition.", + description: + "Perform end-to-end test of SSO flow using provided auth connector definition", args: { name: "filename", - description: "Connector resource definition filename. Empty for stdin.", + description: + "Connector resource definition filename. Empty for stdin", isOptional: true, template: "filepaths", - } + }, }, - ] + ], }, /* tctl version */ { name: "version", - description: "Print the version of your tctl binary." - } + description: "Print the version of your tctl binary", + }, ], options: [ { name: ["--help", "-h"], - description: "Show help.", + description: "Show help", isPersistent: true, }, { @@ -1755,37 +1920,38 @@ const completionSpec: Fig.Spec = { }, { name: ["--config", "-c"], - description: "Path to a configuration file [/etc/teleport.yaml].", + description: "Path to a configuration file [/etc/teleport.yaml]", isPersistent: true, args: { name: "config", - template: "filepaths" + template: "filepaths", }, }, { - name: ["--auth-server"], + name: "--auth-server", description: "Attempts to connect to specific auth/proxy address(es)", isPersistent: true, args: { name: "auth-server", - } + }, }, { name: ["--identity", "-i"], - description: "Path to an identity file.", + description: "Path to an identity file", isPersistent: true, args: { name: "identity", - template: "filepaths" + template: "filepaths", }, }, { - name: ["--insecure"], - description: "When specifying a proxy address in --auth-server, do not verify its TLS certificate.", + name: "--insecure", + description: + "When specifying a proxy address in --auth-server, do not verify its TLS certificate", isPersistent: true, - } + }, ], // Only uncomment if tctl takes an argument // args: {} }; -export default completionSpec; \ No newline at end of file +export default completionSpec; From d861bbb4302d6365a4064784713927c65da6127d Mon Sep 17 00:00:00 2001 From: Kane Petra Date: Mon, 30 Oct 2023 15:30:54 +0100 Subject: [PATCH 4/4] feat(tp): commits got messed up --- src/tctl.ts | 828 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 527 insertions(+), 301 deletions(-) diff --git a/src/tctl.ts b/src/tctl.ts index cdc5bb6e93e8..b2359f1a411a 100644 --- a/src/tctl.ts +++ b/src/tctl.ts @@ -1,185 +1,287 @@ -import { type } from "node:os"; - // Barebones Teleport resource definitions, ensuring type safety and autocomplete type GenericResource = { + kind?: string; metadata: { name: string; description: string; }; }; -type User = object & GenericResource; +type Node = { + spec: { + hostname: string; + }; +} & GenericResource; -type Role = object & GenericResource; +enum AccessRequestState { + "NONE", + "PENDING", + "DENIED", + "APPROVED", +} + +type AccessRequest = { + spec: { + user: string; + roles: string[]; + request_reason: string; + state: number; + }; +} & GenericResource; -type App = object & GenericResource; +type User = { + spec: { + created_by: { + time: string; + user: { + name: string; + }; + }; + status: { + is_locked: boolean; + lock_expires: string; + }; + }; +} & GenericResource; -type Db = object & GenericResource; +type Lock = { + spec: { + target: { + user: string; + }; + created_by: string; + expires: string; + }; +} & GenericResource; -type AccessRequest = object & GenericResource; +type Role = object & GenericResource; type Token = object & GenericResource; type Bot = object & GenericResource; -type WindowsDesktop = object & GenericResource; - type ACL = object & GenericResource; type Alert = object & GenericResource; -type Device = object & GenericResource; - -type Connector = object & GenericResource; - type Namespace = object & GenericResource; type Cluster = { kube_cluster_name: string; }; -const teleportAccountsGenerator: Fig.Generator = { - script: "tctl get users --format json", - postProcess: function (out) { - const users = JSON.parse(out); - - return users.map((user: User) => { - return { - name: user.metadata.name, - description: user.metadata.description, - }; - }); - }, +const commaQueryTerm = (curr) => { + return curr.split(",").pop(); }; -const teleportKubernetesClustersGenerator: Fig.Generator = { - script: "tsh kube ls --format json", - postProcess: function (out) { - const clusters = JSON.parse(out); +// Prefix is used as sometimes Teleport wants the "type" with the request, e.g. tctl get user/USERNAME (user is the prefix here) +const resourcePostProcesserBuilder = (prefix: string = "") => { + return (out: string, tokens: string[]): Fig.Suggestion[] => { + const resources = JSON.parse(out); - return clusters.map((cluster: Cluster) => { - return { - name: cluster.kube_cluster_name, - description: "Kubernetes cluster connected to Teleport", - }; - }); - }, -}; + const postProcesser = resources + .map((resource: GenericResource): Fig.Suggestion => { + if (resource.kind === "node") { + const node = resource as Node; -const commaQueryTerm = (curr) => { - if (curr.includes(",")) { - return curr.slice(curr.lastIndexOf(",") + 1); - } else { - return curr; - } -}; + return { + name: `${prefix}${node.spec.hostname}`, -const filterRoles = (currentRoles: string, allRoles: Role[]) => { - let currentRolesString = currentRoles; + // If there is a resource prefix, we do not need to use the UID (name) because its already unique + insertValue: prefix + ? `${prefix}${node.spec.hostname}` + : node.metadata.name, - if (currentRoles.includes("=")) { - currentRolesString = currentRoles.split("=")[1]; - } + description: "Inserts UUID: " + node.metadata.name, + priority: 65, + }; + } - const filterable = currentRolesString.split(","); + if (resource.kind === "lock") { + const lock = resource as Lock; - return allRoles.filter((role: Role) => { - return !filterable.includes(role.metadata.name); - }); + return { + name: `${resource.kind}/${lock.metadata.name}`, + description: "Created by: " + lock.spec.created_by, + priority: 65, + }; + } + + if (resource.kind === "access_request") { + const request = resource as AccessRequest; + + return { + name: `${prefix}${AccessRequestState[request.spec.state]} ${ + request.spec.user + }`, + insertValue: request.metadata.name, + description: `Requests: ${request.spec.roles.join(", ")}`, + priority: 76, + }; + } + + if (resource.kind === "user") { + const user = resource as User; + const locked = user.spec.status.is_locked; + const lockedExpires = user.spec.status.lock_expires; + + let description = locked + ? `User locked until ${lockedExpires}` + : user.metadata.description; + + if (!description) { + const created = new Date( + user.spec.created_by.time + ).toLocaleDateString(); + const creator = user.spec.created_by.user.name; + + description = `${creator} created this user on ${created}`; + } + + return { + name: `${prefix}${user.metadata.name}`, + icon: locked + ? "fig://icon?type=box&=FF0000&badge=🚫" + : "fig://icon?type=box", + description, + priority: 65, + }; + } + + return { + name: `${prefix}${resource.metadata.name}`, + description: resource.metadata.description, + priority: 65, + }; + }) + .filter((suggestion: Fig.Suggestion) => { + // The last token always contains the resources + const lastToken = tokens[tokens.length - 1]; + + // We remove the resources that we are already listing, this wont impact single resource selection options + return !lastToken.includes(suggestion.name.toString()); + }); + + return postProcesser; + }; }; -const teleportRolesGenerator: Fig.Generator = { - script: "tctl get roles --format json", - getQueryTerm: commaQueryTerm, - postProcess: function (out, tokens) { - const roles = JSON.parse(out); +const tctlGetGenerator = ( + resource: string, + canBeMultiple: boolean = false +): Fig.Generator => { + return { + script: `tctl get ${resource} --format json`, + getQueryTerm: canBeMultiple ? commaQueryTerm : undefined, + postProcess: resourcePostProcesserBuilder(), + }; +}; - const filteredRoles = filterRoles(tokens[tokens.length - 1], roles); +const tshListGenerator = (resource: string): Fig.Generator => { + return { + script: `tsh ${resource} ls --format json`, + postProcess: resourcePostProcesserBuilder(), + }; +}; - return filteredRoles.map((role: Role) => { - return { - name: role.metadata.name, - description: role.metadata.description, - }; - }); +const teleportGenerators: Record = { + yamlFiles: { + template: "filepaths", + // Only show YAML files and directories + filterTemplateSuggestions: function (paths) { + return paths.filter( + (file) => + file.name.endsWith("/") || + file.name.endsWith(".yaml") || + file.name.endsWith(".yml") + ); + }, }, + + // Shorthand suggestion generators + role: tctlGetGenerator("roles"), + roles: tctlGetGenerator("roles", true), + user: tctlGetGenerator("user"), + windows_desktop: tctlGetGenerator("windows_desktop"), + node: tctlGetGenerator("node"), + device: tctlGetGenerator("device"), + connector: tctlGetGenerator("connector"), + kube: tshListGenerator("kube"), + apps: tshListGenerator("apps"), + db: tshListGenerator("db"), + request: tshListGenerator("request"), + tokens: tshListGenerator("tokens"), }; -const teleportAppGenerator: Fig.Generator = { - script: "tsh apps ls --format json", - postProcess: function (out) { - const apps = JSON.parse(out); - return apps.map((app: App) => { - return { - name: app.metadata.name, - description: app.metadata.description, - }; - }); +const teleportOptions: Record = { + ttl: { + name: "--ttl", + description: "Set the time to live, default is 1h0m0s, maximum is 48h0m0s", + args: { + name: "10h10m10s", + description: "Relative duration like 5s, can be chained like 1h10m10s", + }, }, -}; -const teleportDatabaseGenerator: Fig.Generator = { - script: "tsh db ls --format json", - postProcess: function (out) { - const dbs = JSON.parse(out); - return dbs.map((db: Db) => { - return { - name: db.metadata.name, - description: db.metadata.description, - }; - }); + format: { + name: "--format", + description: "Output format. One of: [text, json, yaml]", + args: { + name: "format", + suggestions: ["text", "json", "yaml"], + default: "yaml", + }, }, -}; -const teleportRequestGenerator: Fig.Generator = { - script: "tctl request ls --format json", - postProcess: function (out) { - const requests = JSON.parse(out); - return requests.map((request: AccessRequest) => { - return { - name: request.metadata.name, - description: request.metadata.description, - }; - }); + labels: { + name: "--labels", + description: "Which labels to add to the resource", + args: { + name: "label1=value1,label2=value2", + }, }, -}; -const teleportTokenGenerator: Fig.Generator = { - script: "tctl tokens ls --format json", - postProcess: function (out) { - const tokens = JSON.parse(out); - return tokens.map((token: Token) => { - return { - name: token.metadata.name, - description: token.metadata.description, - }; - }); + reason: { + name: "--reason", + description: "Optional reason message", + insertValue: "--reason '{cursor}'", + args: { + name: "reason", + }, }, -}; -const teleportWindowsDesktopGenerator: Fig.Generator = { - script: "tctl get windows_desktop --format json", - postProcess: function (out) { - const desktops = JSON.parse(out); - return desktops.map((desktop: WindowsDesktop) => { - return { - name: desktop.metadata.name, - description: desktop.metadata.description, - }; - }); + roles: { + name: "--roles", + description: "Comma seperated list of roles", + args: { + name: "role1,role2", + generators: teleportGenerators.roles, + }, }, -}; -const teleportFormatOption: Fig.Option = { - name: "--format", - description: "Output format. One of: [text, json, yaml]", - args: { - name: "format", - suggestions: ["text", "json", "yaml"], - default: "text", + logins: { + name: "--logins", + description: "List of allowed SSH logins", + args: { + name: "login1,login2", + }, }, }; +const filterRoles = (currentRoles: string, allRoles: Role[]) => { + let currentRolesString = currentRoles; + + if (currentRoles.includes("=")) { + currentRolesString = currentRoles.split("=")[1]; + } + + const filterable = currentRolesString.split(","); + + return allRoles.filter((role: Role) => { + return !filterable.includes(role.metadata.name); + }); +}; + const teleportBotsGenerator: Fig.Generator = { script: "tctl bots ls --format json", postProcess: function (out) { @@ -219,37 +321,11 @@ const teleportAlertGenerator: Fig.Generator = { }, }; -const teleportDeviceGenerator: Fig.Generator = { - script: "tctl get device --format json", - postProcess: function (out) { - const devices = JSON.parse(out); - return devices.map((device: Device) => { - return { - name: device.metadata.name, - description: device.metadata.description, - }; - }); - }, -}; - -const teleportSAMLConnectorGenerator: Fig.Generator = { - script: "tctl get connector --format json", - postProcess: function (out) { - const connectors = JSON.parse(out); - return connectors.map((connector: Connector) => { - return { - name: connector.metadata.name, - description: connector.metadata.description, - }; - }); - }, -}; - const teleportGetResourcesGenerator: Fig.Generator = { trigger: (current, old) => { - return true; + return current.lastIndexOf("/") > old.lastIndexOf("/"); }, - custom: async (tokens, executeShellCommand) => { + custom: async (tokens, executeShellCommand): Promise => { const standardSuggestions = [ "user", "role", @@ -264,10 +340,12 @@ const teleportGetResourcesGenerator: Fig.Generator = { "lock", "all", ]; + const respondSuggestions = standardSuggestions.map((suggestion) => { return { name: suggestion, description: "Get a " + suggestion, + priority: 100, }; }); @@ -281,52 +359,68 @@ const teleportGetResourcesGenerator: Fig.Generator = { .find((token) => standardSuggestions.includes(token.split("/")[0])) .split("/")[0]; + // Only show suggestions for resources that are supported by tctl if (standardSuggestions.find((sug) => sug === resource) == undefined) return respondSuggestions; + if (["cluster_auth_preference", "all"].includes(resource)) return []; // This is what tctl expects const resources = await executeShellCommand( `tctl get ${resource} --format json` ); + const parsedResources = JSON.parse(resources); - return parsedResources.map((parsedResource: GenericResource) => { - return { - name: `${resource}/${parsedResource.metadata.name}`, - }; - }); + let parsedLocks: Lock[] = []; + + if (["lock", "user"].includes(resource)) { + const locks = await executeShellCommand(`tctl get locks --format json`); + parsedLocks = JSON.parse(locks); + } + + const postProcessResource = resourcePostProcesserBuilder(`${resource}/`); + + if (resource === "user") { + const users = parsedResources as User[]; + + users.forEach((user) => { + parsedLocks.find((lock) => { + if (lock.spec.target.user === user.metadata.name) { + user.spec.status.is_locked = true; + user.spec.status.lock_expires = lock.spec.expires; + } + }); + }); + + return postProcessResource(JSON.stringify(users), tokens); + } + + return postProcessResource(JSON.stringify(parsedResources), tokens); } return respondSuggestions; }, }; -/* tctl lock --help - --user Name of a Teleport user to disable. - --role Name of a Teleport role to disable. - --login Name of a local UNIX user to disable. - --mfa-device UUID of a user MFA device to disable. - --windows-desktop Name of a Windows desktop to disable. - --access-request UUID of an access request to disable. - --device UUID of a trusted device to disable. - --message Message to display to locked-out users. - --expires Time point (RFC3339) when the lock expires. - --ttl Time duration after which the lock expires. - --server-id UUID of a Teleport server to disable. -*/ - const completionSpec: Fig.Spec = { name: "tctl", description: "Admin tool for the Teleport Access Platform", + args: {}, + requiresSubcommand: true, subcommands: [ /* tctl help */ { name: "help", description: "Show help", + priority: 100, }, /* tctl users */ { name: "users", + description: "Manage user accounts", + requiresSubcommand: true, + args: {}, + priority: 100, subcommands: [ { name: "add", @@ -336,69 +430,86 @@ const completionSpec: Fig.Spec = { description: "Teleport user account name", }, options: [ - { - name: "--logins", - description: "List of allowed SSH logins for the new user", - }, + teleportOptions.ttl, + teleportOptions.roles, + teleportOptions.logins, { name: "--windows-logins", description: "List of allowed Windows logins for the new user", + args: { + name: "login1,login2", + }, }, { name: "--kubernetes-users", description: "List of allowed Kubernetes users for the new user", + args: { + name: "value1,value2", + }, }, { name: "--kubernetes-groups", description: "List of allowed Kubernetes groups for the new user", + args: { + name: "group1,group2", + }, }, { name: "--db-users", description: "List of allowed database users for the new user", + args: { + name: "user1,user2", + }, }, { name: "--db-names", description: "List of allowed database names for the new user", + args: { + name: "value1,value2", + }, }, { name: "--db-roles", description: "List of database roles for automatic database user provisioning", + args: { + name: "name1,name2", + }, }, { name: "--aws-role-arns", description: "List of allowed AWS role ARNs for the new user", + args: { + name: "value1,value2", + }, }, { name: "--azure-identities", description: "List of allowed Azure identities for the new user", + args: { + name: "identity1,identity2", + }, }, { name: "--gcp-service-accounts", description: "List of allowed GCP service accounts for the new user", + args: { + name: "account1,account2", + }, }, { name: "--host-user-uid", description: "UID for auto provisioned host users to use", + args: { + name: "user-id", + }, }, { name: "--host-user-gid", description: "GID for auto provisioned host users to use", - }, - { - name: "--ttl", - description: - "Set expiration time for token, default is 1h0m0s, maximum is 48h0m0s", - }, - { - name: "--roles", - description: - "List of roles for the new user to assume. Comma seperated", - isRequired: true, - isRepeatable: true, args: { - generators: teleportRolesGenerator, + name: "group-id", }, }, ], @@ -410,81 +521,119 @@ const completionSpec: Fig.Spec = { { name: "--set-roles", description: - "List of roles for the user to assume, replaces current roles. Comma seperated", + "List of roles for the user to assume, replaces current roles", args: { - generators: teleportRolesGenerator, + name: "role1,role2", + generators: teleportGenerators.roles, }, }, { name: "--set-logins", description: "List of allowed SSH logins for the user, replaces current logins", + args: { + name: "value1,value2", + }, }, { name: "--set-windows-logins", description: "List of allowed Windows logins for the user, replaces current Windows logins", + args: { + name: "value1,value2", + }, }, { name: "--set-kubernetes-users", description: "List of allowed Kubernetes users for the user, replaces current Kubernetes users", + args: { + name: "value1,value2", + }, }, { name: "--set-kubernetes-groups", description: "List of allowed Kubernetes groups for the user, replaces current Kubernetes groups", + args: { + name: "value1,value2", + }, }, { name: "--set-db-users", description: "List of allowed database users for the user, replaces current database users", + args: { + name: "value1,value2", + }, }, { name: "--set-db-names", description: "List of allowed database names for the user, replaces current database names", + args: { + name: "value1,value2", + }, }, { name: "--set-db-roles", description: "List of allowed database roles for automatic database user provisioning, replaces current database roles", + args: { + name: "value1,value2", + }, }, { name: "--set-aws-role-arns", description: "List of allowed AWS role ARNs for the user, replaces current AWS role ARNs", + args: { + name: "value1,value2", + }, }, { name: "--set-azure-identities", description: "List of allowed Azure identities for the user, replaces current Azure identities", + args: { + name: "value1,value2", + }, }, { name: "--set-gcp-service-accounts", description: "List of allowed GCP service accounts for the user, replaces current service accounts", + args: { + name: "value1,value2", + }, }, { name: "--set-host-user-uid", description: "UID for auto provisioned host users to use. Value can be reset by providing an empty string", + args: { + name: "user-id", + }, }, { name: "--set-host-user-gid", description: "GID for auto provisioned host users to use. Value can be reset by providing an empty string", + args: { + name: "group-id", + }, }, ], args: { name: "account", description: "Teleport user account name", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, }, { name: "ls", description: "Lists all user accounts", + options: [teleportOptions.format], }, { name: "rm", @@ -493,7 +642,7 @@ const completionSpec: Fig.Spec = { name: "account", description: "Teleport user account name", isVariadic: true, - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, }, { @@ -503,7 +652,7 @@ const completionSpec: Fig.Spec = { args: { name: "account", description: "Teleport user account name", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, }, ], @@ -511,17 +660,22 @@ const completionSpec: Fig.Spec = { /* tctl nodes */ { name: "nodes", + priority: 100, description: "Issue invites for other nodes to join the cluster", + requiresSubcommand: true, subcommands: [ { name: "add", description: "Generate a node invitation token", + args: {}, options: [ + teleportOptions.ttl, { name: "--roles", description: "Comma-separated list of roles for the new node to assume", args: { + name: "role1,role2", generators: { getQueryTerm: commaQueryTerm, trigger: (current, old) => { @@ -552,17 +706,13 @@ const completionSpec: Fig.Spec = { }, }, }, - { - name: "--ttl", - description: - "Time to live for a generated token, default is 0h30m0s, maximum is 48h0m0s", - }, ], }, { name: "ls", description: "List all active SSH nodes within the cluster", options: [ + teleportOptions.format, { name: "--namespace", description: "Namespace of the nodes", @@ -592,14 +742,18 @@ const completionSpec: Fig.Spec = { /* tctl tokens */ { name: "tokens", + priority: 100, description: "Manage invitation tokens", + requiresSubcommand: true, + args: {}, subcommands: [ { name: "add", description: "Create a invitation token", args: {}, options: [ - teleportFormatOption, + teleportOptions.format, + teleportOptions.ttl, { name: "--type", description: "Type(s) of token to add", @@ -699,14 +853,6 @@ const completionSpec: Fig.Spec = { name: "label1=value1,label2=value2", }, }, - { - name: "--ttl", - description: - "Set expiration time for token, default is 30 minutes", - args: { - name: "30m", - }, - }, { name: "--app-name", description: "Name of the application to add", @@ -779,7 +925,7 @@ const completionSpec: Fig.Spec = { { name: "ls", description: "List node and user invitation tokens", - options: [teleportFormatOption], + options: [teleportOptions.format], }, ], }, @@ -788,6 +934,7 @@ const completionSpec: Fig.Spec = { name: "auth", description: "Operations with user and host certificate authorities (CAs)", + priority: 100, args: {}, subcommands: [ { @@ -801,6 +948,9 @@ const completionSpec: Fig.Spec = { { name: "--fingerprint", description: "Filter authority by fingerprint", + args: { + name: "fingerprint", + }, }, { name: "--compat", @@ -841,10 +991,11 @@ const completionSpec: Fig.Spec = { { name: "--user", description: "Teleport user name", + priority: 100, isRequired: true, args: { name: "user", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, }, { @@ -857,6 +1008,7 @@ const completionSpec: Fig.Spec = { { name: ["--out", "-o"], description: "Identity output", + priority: 99, isRequired: true, args: { name: "out", @@ -873,11 +1025,8 @@ const completionSpec: Fig.Spec = { }, }, { - name: "--ttl", + ...teleportOptions.ttl, description: "TTL (time to live) for the generated certificate", - args: { - name: "ttl", - }, }, { name: "--compat", @@ -920,66 +1069,95 @@ const completionSpec: Fig.Spec = { 'Kubernetes cluster to generate identity file for when --format is set to "kubernetes"', args: { name: "name", - generators: teleportKubernetesClustersGenerator, + generators: { + ...teleportGenerators.kube, + postProcess: function (out) { + const clusters = JSON.parse(out); + + return clusters.map((cluster: Cluster) => { + return { + name: cluster.kube_cluster_name, + description: "Kubernetes cluster connected to Teleport", + }; + }); + }, + }, }, }, { name: "--app-name", description: 'Application to generate identity file for. Mutually exclusive with "--db-service"', + exclusiveOn: ["--db-service"], args: { name: "name", - generators: teleportAppGenerator, + generators: teleportGenerators.apps, }, }, { name: "--db-service", description: 'Database to generate identity file for. Mutually exclusive with "--app-name"', + exclusiveOn: ["--app-name"], args: { name: "service", - generators: teleportDatabaseGenerator, + generators: teleportGenerators.db, }, }, { name: "--db-user", description: 'Database user placed on the identity file. Only used when "--db-service" is set', + dependsOn: ["--db-service"], + args: { + name: "user", + }, }, { name: "--db-name", description: 'Database name placed on the identity file. Only used when "--db-service" is set', + dependsOn: ["--db-service"], + args: { + name: "name", + }, }, { name: "--windows-user", description: 'Window user placed on the identity file. Only used when --format is set to "windows"', + args: { + name: "user", + }, }, { name: "--windows-domain", description: 'Active Directory domain for which this cert is valid. Only used when --format is set to "windows"', + args: { + name: "domain", + }, }, { name: "--windows-sid", description: 'Optional Security Identifier to embed in the certificate. Only used when --format is set to "windows"', + args: { + name: "security-id", + }, }, ], }, { name: "rotate", description: "Rotate certificate authorities in the cluster", + args: {}, + isDangerous: true, options: [ { + ...teleportOptions.ttl, name: "--grace-period", - description: - "Grace period keeps previous certificate authorities signatures valid, if set to 0 will force users to re-login and nodes to re-register", - args: { - name: "duration", - description: "Relative duration like 5s, 2m, or 3h", - }, + description: "Grace period keeps previous CA valid", }, { name: "--manual", @@ -1025,12 +1203,13 @@ const completionSpec: Fig.Spec = { { name: "ls", description: "List connected auth servers", - options: [teleportFormatOption], + options: [teleportOptions.format], }, { name: "crl", description: "Export empty certificate revocation list (CRL) for certificate authorities", + args: {}, options: [ { name: "--type", @@ -1050,13 +1229,14 @@ const completionSpec: Fig.Spec = { { name: "get", description: "Get a resource", + priority: 100, args: { - name: "resource", - description: "Resource to get", + name: "type/name", + description: "Resource to get (e.g. user/bob)", generators: teleportGetResourcesGenerator, }, options: [ - teleportFormatOption, + teleportOptions.format, { name: "--with-secrets", description: @@ -1072,11 +1252,13 @@ const completionSpec: Fig.Spec = { { name: "status", description: "Report cluster status", + priority: 100, }, /* tctl top */ { name: "top", description: "Report cluster status", + priority: 100, args: [ { name: "diag-address", @@ -1093,10 +1275,12 @@ const completionSpec: Fig.Spec = { name: ["requests", "request"], description: "Manage access requests", args: {}, + priority: 100, subcommands: [ { name: "ls", description: "Show active access requests", + options: [teleportOptions.format], }, { name: "get", @@ -1104,8 +1288,9 @@ const completionSpec: Fig.Spec = { args: { name: "request", description: "Access request ID", - generators: teleportRequestGenerator, + generators: teleportGenerators.request, }, + options: [teleportOptions.format], }, { name: "approve", @@ -1113,7 +1298,7 @@ const completionSpec: Fig.Spec = { args: { name: "request", description: "Access request ID", - generators: teleportRequestGenerator, + generators: teleportGenerators.request, }, }, { @@ -1122,7 +1307,7 @@ const completionSpec: Fig.Spec = { args: { name: "request", description: "Access request ID", - generators: teleportRequestGenerator, + generators: teleportGenerators.request, }, }, { @@ -1131,21 +1316,11 @@ const completionSpec: Fig.Spec = { args: { name: "username", description: "Name of target user", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, options: [ - { - name: "--roles", - description: "Roles to be requested", - args: { - name: "roles", - generators: teleportRolesGenerator, - }, - }, - { - name: "--reason", - description: "Optional reason message", - }, + teleportOptions.reason, + teleportOptions.roles, { name: "--resource", description: "Resource ID to be requested", @@ -1172,7 +1347,7 @@ const completionSpec: Fig.Spec = { args: { name: "request-id", description: "Access request ID", - generators: teleportRequestGenerator, + generators: teleportGenerators.request, }, }, { @@ -1185,7 +1360,7 @@ const completionSpec: Fig.Spec = { isRequired: true, args: { name: "author", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, }, { @@ -1200,7 +1375,7 @@ const completionSpec: Fig.Spec = { args: { name: "request-id", description: "Access request ID", - generators: teleportRequestGenerator, + generators: teleportGenerators.request, }, }, ], @@ -1209,12 +1384,13 @@ const completionSpec: Fig.Spec = { { name: "apps", description: "Operate on applications registered with the cluster", - args: {}, requiresSubcommand: true, + priority: 100, subcommands: [ { name: "ls", description: "List all applications registered with the cluster", + options: [teleportOptions.format], }, ], }, @@ -1222,12 +1398,13 @@ const completionSpec: Fig.Spec = { { name: "db", description: "Operate on databases registered with the cluster", - args: {}, requiresSubcommand: true, + priority: 100, subcommands: [ { name: "ls", description: "List all databases registered with the cluster", + options: [teleportOptions.format], }, ], }, @@ -1235,13 +1412,14 @@ const completionSpec: Fig.Spec = { { name: "kube", description: "Operate on registered Kubernetes clusters", - args: {}, requiresSubcommand: true, + priority: 100, subcommands: [ { name: "ls", description: "List all Kubernetes clusters registered with the cluster", + options: [teleportOptions.format], }, ], }, @@ -1249,12 +1427,13 @@ const completionSpec: Fig.Spec = { { name: "windows_desktops", description: "Operate on registered Windows desktops", - args: {}, requiresSubcommand: true, + priority: 100, subcommands: [ { name: "ls", description: "List all Windows desktops registered with the cluster", + options: [teleportOptions.format], }, ], }, @@ -1262,13 +1441,13 @@ const completionSpec: Fig.Spec = { { name: "proxy", description: "Operations with information for cluster proxies", - args: {}, requiresSubcommand: true, + priority: 100, subcommands: [ { name: "ls", description: "Lists proxies connected to the cluster", - options: [teleportFormatOption], + options: [teleportOptions.format], }, ], }, @@ -1287,13 +1466,14 @@ const completionSpec: Fig.Spec = { name: "lock", description: "Create a new lock", args: {}, + priority: 100, options: [ { name: "--user", description: "Name of a Teleport user to disable", args: { name: "user", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, }, { @@ -1301,23 +1481,29 @@ const completionSpec: Fig.Spec = { description: "Name of a Teleport role to disable", args: { name: "role", - generators: teleportRolesGenerator, + generators: teleportGenerators.role, }, }, { name: "--login", description: "Name of a local UNIX user to disable", + args: { + name: "login", + }, }, { name: "--mfa-device", description: "UUID of a user MFA device to disable", + args: { + name: "device", + }, }, { name: "--windows-desktop", description: "Name of a Windows desktop to disable", args: { name: "desktop", - generators: teleportWindowsDesktopGenerator, + generators: teleportGenerators.windows_desktop, }, }, { @@ -1325,7 +1511,7 @@ const completionSpec: Fig.Spec = { description: "UUID of an access request to disable", args: { name: "request", - generators: teleportRequestGenerator, + generators: teleportGenerators.request, }, }, { @@ -1345,16 +1531,16 @@ const completionSpec: Fig.Spec = { }, }, { - name: "--ttl", + ...teleportOptions.ttl, description: "Time duration after which the lock expires", - args: { - name: "duration", - description: "Time duration after which the lock expires", - }, }, { name: "--server-id", description: "UUID of a Teleport server to disable", + args: { + name: "server-uuid", + generators: teleportGenerators.node, + }, }, ], }, @@ -1364,11 +1550,14 @@ const completionSpec: Fig.Spec = { description: "Operate on certificate renewal bots registered with the cluster", requiresSubcommand: true, + args: {}, + priority: 100, subcommands: [ { name: "ls", description: "List all certificate renewal bots registered with the cluster", + options: [teleportOptions.format], }, { name: "add", @@ -1376,33 +1565,31 @@ const completionSpec: Fig.Spec = { args: { name: "name", description: "A name to uniquely identify this bot in the cluster", - }, - options: [ - { - name: "--roles", - description: "Roles the bot is able to assume", - isRequired: true, - args: { - name: "roles", - generators: teleportRolesGenerator, + generators: { + ...teleportBotsGenerator, + postProcess: function (out) { + const bots = JSON.parse(out); + return bots.map((bot: Bot) => { + return { + name: bot.metadata.name.slice(4), + description: "A bot with this name already exists", + }; + }); }, }, - { - name: "--ttl", - description: "TTL for the bot join token", - }, + }, + options: [ + teleportOptions.ttl, + teleportOptions.roles, + teleportOptions.logins, { name: "--token", description: "Name of an existing token to use", args: { name: "token", - generators: teleportTokenGenerator, + generators: teleportGenerators.tokens, }, }, - { - name: "--logins", - description: "List of allowed SSH logins for the bot user", - }, ], }, { @@ -1422,6 +1609,7 @@ const completionSpec: Fig.Spec = { name: "inventory", description: "Manage Teleport instance inventory", requiresSubcommand: true, + priority: 100, subcommands: [ { name: "status", @@ -1496,6 +1684,7 @@ const completionSpec: Fig.Spec = { name: "recordings", description: "View and control session recordings", requiresSubcommand: true, + priority: 100, subcommands: [ { name: "ls", @@ -1508,15 +1697,17 @@ const completionSpec: Fig.Spec = { name: "alerts", description: "Manage cluster alerts", requiresSubcommand: true, + args: {}, + priority: 100, subcommands: [ { name: "list", description: "List cluster alerts", options: [ - teleportFormatOption, + teleportOptions.format, { name: "--labels", - description: "List of comma separated labels to filter by labels", + description: "Filter by labels", args: { name: "label1=value1,label2=value2", }, @@ -1534,7 +1725,7 @@ const completionSpec: Fig.Spec = { options: [ { name: "--labels", - description: "List of comma separated labels to filter by labels", + description: "Which labels should this alert have", args: { name: "label1=value1,label2=value2", }, @@ -1548,7 +1739,7 @@ const completionSpec: Fig.Spec = { }, }, { - name: "--ttl", + ...teleportOptions.ttl, description: "Time duration after which the alert expires (default 24h)", }, @@ -1573,12 +1764,9 @@ const completionSpec: Fig.Spec = { generators: teleportAlertGenerator, }, options: [ + ...[teleportOptions.reason], { - name: "--reason", - description: "The reason for acknowledging the cluster alert", - }, - { - name: "--ttl", + ...teleportOptions.ttl, description: "Time duration after which the alert expires (default 24h)", }, @@ -1594,9 +1782,10 @@ const completionSpec: Fig.Spec = { { name: "create", description: "Create or update a Teleport resource from a YAML file", + priority: 100, args: { name: "filename", - template: "filepaths", + generators: teleportGenerators.yamlFiles, }, options: [ { @@ -1609,6 +1798,7 @@ const completionSpec: Fig.Spec = { { name: "update", description: "Update resource fields", + priority: 100, args: { name: "resource type/resource name", description: "Resource to update", @@ -1617,11 +1807,15 @@ const completionSpec: Fig.Spec = { options: [ { name: "--set-labels", - description: "Set labels", + description: "Replace labels", + args: { + name: "label1=value1,label2=value2", + }, }, { + ...teleportOptions.ttl, name: "--set-ttl", - description: "Set TTL", + description: "Replace TTL", }, ], }, @@ -1629,6 +1823,7 @@ const completionSpec: Fig.Spec = { { name: "edit", description: "Edit a Teleport resource", + priority: 100, args: { name: "resource type/resource name", description: "Resource to edit", @@ -1639,7 +1834,9 @@ const completionSpec: Fig.Spec = { { name: "devices", description: "Register and manage trusted devices", + priority: 100, requiresSubcommand: true, + args: {}, subcommands: [ { name: "add", @@ -1683,7 +1880,7 @@ const completionSpec: Fig.Spec = { args: { name: "device", description: "Device ID", - generators: teleportDeviceGenerator, + generators: teleportGenerators.device, }, }, { @@ -1710,7 +1907,7 @@ const completionSpec: Fig.Spec = { args: { name: "device", description: "Device ID", - generators: teleportDeviceGenerator, + generators: teleportGenerators.device, }, }, ], @@ -1720,14 +1917,17 @@ const completionSpec: Fig.Spec = { name: "saml", description: "Operations on SAML auth connectors", requiresSubcommand: true, + priority: 100, + args: {}, subcommands: [ { name: "export", description: "Export a SAML signing key in .crt format", args: { name: "connector_name", + isOptional: true, description: "Name of the SAML connector to export the key from", - generators: teleportSAMLConnectorGenerator, + generators: teleportGenerators.connector, }, }, ], @@ -1737,6 +1937,8 @@ const completionSpec: Fig.Spec = { name: ["acl", "access-lists"], description: "Manage access lists", requiresSubcommand: true, + priority: 100, + args: {}, subcommands: [ { name: "ls", @@ -1768,7 +1970,7 @@ const completionSpec: Fig.Spec = { { name: "user", description: "The user name", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, { name: "expires", @@ -1796,7 +1998,7 @@ const completionSpec: Fig.Spec = { { name: "user", description: "The user name", - generators: teleportAccountsGenerator, + generators: teleportGenerators.user, }, ], }, @@ -1818,6 +2020,7 @@ const completionSpec: Fig.Spec = { name: "login_rule", description: "Test login rules", requiresSubcommand: true, + priority: 100, subcommands: [ { name: "test", @@ -1831,6 +2034,8 @@ const completionSpec: Fig.Spec = { description: "A family of commands for configuring and testing auth connectors (SSO)", requiresSubcommand: true, + priority: 100, + args: {}, subcommands: [ { name: "configure", @@ -1844,6 +2049,9 @@ const completionSpec: Fig.Spec = { { name: "--name", description: "Connector name", + args: { + name: "name", + }, }, { name: "--teams-to-roles", @@ -1856,26 +2064,44 @@ const completionSpec: Fig.Spec = { { name: "--display", description: "Sets the connector display name", + args: { + name: "display-name", + }, }, { name: "--id", description: "GitHub app client ID", + args: { + name: "id", + }, }, { name: "--secret", description: "GitHub app client secret", + args: { + name: "secret", + }, }, { name: "--endpoint-url", description: "Endpoint URL for GitHub instance", + args: { + name: "endpoint-url", + }, }, { name: "--api-endpoint-url", description: "API endpoint URL for GitHub instance", + args: { + name: "api-endpoint-url", + }, }, { name: "--redirect-url", description: "Authorization callback URL", + args: { + name: "redirect-url", + }, }, { name: "--ignore-missing-roles", @@ -1895,8 +2121,7 @@ const completionSpec: Fig.Spec = { name: "filename", description: "Connector resource definition filename. Empty for stdin", - isOptional: true, - template: "filepaths", + generators: teleportGenerators.yamlFiles, }, }, ], @@ -1904,6 +2129,7 @@ const completionSpec: Fig.Spec = { /* tctl version */ { name: "version", + priority: 100, description: "Print the version of your tctl binary", }, ], @@ -1924,7 +2150,7 @@ const completionSpec: Fig.Spec = { isPersistent: true, args: { name: "config", - template: "filepaths", + generators: teleportGenerators.yamlFiles, }, }, {