parent
48f7bd146f
commit
896a34eb65
@ -0,0 +1,181 @@
|
||||
/*
|
||||
Copyright (c) 2023 Infisical Inc.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/api"
|
||||
"github.com/Infisical/infisical-merge/packages/crypto"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var tokensCmd = &cobra.Command{
|
||||
Use: "service-token",
|
||||
Short: "Manage service tokens",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical service-token",
|
||||
Args: cobra.ExactArgs(0),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
},
|
||||
}
|
||||
|
||||
var tokensCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Used to create service tokens",
|
||||
DisableFlagsInUseLine: true,
|
||||
Example: "infisical service-token create",
|
||||
Args: cobra.ExactArgs(0),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
util.RequireLogin()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// get plain text workspace key
|
||||
loggedInUserDetails, _ := util.GetCurrentLoggedInUserDetails()
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
|
||||
tokenOnly, err := cmd.Flags().GetBool("token-only")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
workspaceId, err := cmd.Flags().GetString("projectId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
if workspaceId == "" {
|
||||
configFile, err := util.GetWorkSpaceFromFile()
|
||||
if err != nil {
|
||||
util.PrintErrorMessageAndExit("Please either run infisical init to connect to a project or pass in project id with --projectId flag")
|
||||
}
|
||||
workspaceId = configFile.WorkspaceId
|
||||
}
|
||||
|
||||
serviceTokenName, err := cmd.Flags().GetString("name")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
scopes, err := cmd.Flags().GetStringSlice("scope")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
if len(scopes) == 0 {
|
||||
util.PrintErrorMessageAndExit("You must define the environments and paths your service token should have access to via the --scope flag")
|
||||
}
|
||||
|
||||
permissions := []api.ScopePermission{}
|
||||
|
||||
for _, scope := range scopes {
|
||||
parts := strings.Split(scope, ":")
|
||||
|
||||
if len(parts) != 2 {
|
||||
fmt.Println("--scope flag is malformed. Each scope flag should be in the following format: <env-slug>:<folder-path>")
|
||||
return
|
||||
}
|
||||
|
||||
permissions = append(permissions, api.ScopePermission{Environment: parts[0], SecretPath: parts[1]})
|
||||
}
|
||||
|
||||
accessLevels, err := cmd.Flags().GetStringSlice("access-level")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag accessLevels")
|
||||
}
|
||||
|
||||
if len(accessLevels) == 0 {
|
||||
util.PrintErrorMessageAndExit("You must define whether your service token can be used to read and or write via the --access-level flag")
|
||||
}
|
||||
|
||||
for _, accessLevel := range accessLevels {
|
||||
if accessLevel != "read" && accessLevel != "write" {
|
||||
util.PrintErrorMessageAndExit("--access-level can only be of values read and write")
|
||||
}
|
||||
}
|
||||
|
||||
workspaceKey, err := util.GetPlainTextWorkspaceKey(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceId)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
newWorkspaceEncryptionKey := make([]byte, 16)
|
||||
_, err = rand.Read(newWorkspaceEncryptionKey)
|
||||
if err != nil {
|
||||
fmt.Println("Error generating random bytes:", err)
|
||||
return
|
||||
}
|
||||
|
||||
newWorkspaceEncryptionKeyHexFormat := hex.EncodeToString(newWorkspaceEncryptionKey)
|
||||
|
||||
// encrypt the workspace key symmetrically
|
||||
encryptedDetails, err := crypto.EncryptSymmetric(workspaceKey, newWorkspaceEncryptionKey)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
// make a call to the api to save the encrypted symmetric key details
|
||||
httpClient := resty.New()
|
||||
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
||||
SetHeader("Accept", "application/json")
|
||||
|
||||
createServiceTokenResponse, err := api.CallCreateServiceToken(httpClient, api.CreateServiceTokenRequest{
|
||||
Name: serviceTokenName,
|
||||
WorkspaceId: workspaceId,
|
||||
Scopes: permissions,
|
||||
ExpiresIn: 0,
|
||||
EncryptedKey: string(workspaceKey),
|
||||
Iv: base64.StdEncoding.EncodeToString(encryptedDetails.Nonce),
|
||||
Tag: base64.StdEncoding.EncodeToString(encryptedDetails.AuthTag),
|
||||
RandomBytes: newWorkspaceEncryptionKeyHexFormat,
|
||||
Permissions: accessLevels,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
serviceToken := createServiceTokenResponse.ServiceToken + "." + newWorkspaceEncryptionKeyHexFormat
|
||||
|
||||
if tokenOnly {
|
||||
fmt.Println(serviceToken)
|
||||
} else {
|
||||
printablePermission := []string{}
|
||||
for _, permission := range permissions {
|
||||
printablePermission = append(printablePermission, fmt.Sprintf("([environment: %v] [path: %v])", permission.Environment, permission.SecretPath))
|
||||
}
|
||||
|
||||
fmt.Printf("New service token created\n")
|
||||
fmt.Printf("Name: %v\n", serviceTokenName)
|
||||
fmt.Printf("Project ID: %v\n", workspaceId)
|
||||
fmt.Printf("Access type: [%v]\n", strings.Join(accessLevels, ", "))
|
||||
fmt.Printf("Permission(s): %v\n", strings.Join(printablePermission, ", "))
|
||||
fmt.Printf("Service Token: %v\n", serviceToken)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
tokensCreateCmd.Flags().String("projectId", "", "The project ID you'd like to create the service token for. Default: will use linked Infisical project in .infisical.json")
|
||||
tokensCreateCmd.Flags().StringSliceP("scope", "s", []string{}, "Environment and secret path. Example format: <env-slug>:<folder-path>")
|
||||
tokensCreateCmd.Flags().StringP("name", "n", "Service token generated via CLI", "Service token name")
|
||||
tokensCreateCmd.Flags().StringSliceP("access-level", "a", []string{}, "The type of access the service token should have. Can be 'read' and or 'write'")
|
||||
tokensCreateCmd.Flags().Bool("token-only", false, "When true, only the service token will be printed")
|
||||
|
||||
tokensCmd.AddCommand(tokensCreateCmd)
|
||||
|
||||
rootCmd.AddCommand(tokensCmd)
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
---
|
||||
title: "infisical service-token"
|
||||
description: "Manage Infisical service tokens"
|
||||
---
|
||||
|
||||
```bash
|
||||
infisical service-token create --scope=dev:/global --scope=dev:/backend --access-level=read --access-level=write
|
||||
```
|
||||
|
||||
## Description
|
||||
The Infisical `service-token` command allows you to manage service tokens for a given Infisical project.
|
||||
With this command can create, view and delete service tokens.
|
||||
|
||||
<Accordion title="service-token create" defaultOpen="true">
|
||||
Use this command to create a service token
|
||||
|
||||
```bash
|
||||
$ infisical service-token create --scope=dev:/backend/** --access-level=read --access-level=write
|
||||
```
|
||||
|
||||
### Flags
|
||||
<Accordion title="--scope">
|
||||
```bash
|
||||
infisical service-token create --scope=dev:/global --scope=dev:/backend/** --access-level=read
|
||||
```
|
||||
|
||||
Use the scope flag to define which environments and paths your service token should be authorized to access.
|
||||
|
||||
The value of your scope flag should be in the following `<environment slug>:<path>`.
|
||||
Here, `environment slug` refers to the slug name of the environment, and `path` indicates the folder path where your secrets are stored.
|
||||
|
||||
For specifying multiple scopes, you can use multiple --scope flags.
|
||||
|
||||
<Info>
|
||||
The `path` can be a Glob pattern
|
||||
</Info>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="--projectId">
|
||||
```bash
|
||||
infisical service-token create --scope=dev:/global --access-level=read --projectId=63cefb15c8d3175601cfa989
|
||||
```
|
||||
|
||||
The project ID you'd like to create the service token for.
|
||||
By default, the CLI will attempt to use the linked Infisical project in `.infisical.json` generated by `infisical init` command.
|
||||
</Accordion>
|
||||
<Accordion title="--name">
|
||||
```bash
|
||||
infisical service-token create --scope=dev:/global --access-level=read --name service-token-name
|
||||
```
|
||||
|
||||
Service token name
|
||||
|
||||
Default: `Service token generated via CLI`
|
||||
</Accordion>
|
||||
<Accordion title="--access-level">
|
||||
```bash
|
||||
infisical service-token create --scope=dev:/global --access-level=read --access-level=write
|
||||
```
|
||||
|
||||
The type of access the service token should have. Can be `read` and or `write`
|
||||
</Accordion>
|
||||
<Accordion title="--token-only">
|
||||
```bash
|
||||
infisical service-token create --scope=dev:/global --access-level=read --access-level=write --token-only
|
||||
```
|
||||
|
||||
When true, only the service token will be printed
|
||||
|
||||
Default: `false`
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
Loading…
Reference in new issue