Skip to content

Commit

Permalink
Issue #3717 - NodeSecret: 'hzn secretsmanager secrete' commands suppo…
Browse files Browse the repository at this point in the history
…rts node secret format

Signed-off-by: Le Zhang <[email protected]>
  • Loading branch information
LiilyZhang committed Aug 11, 2023
1 parent 58b4010 commit 7cfb050
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 12 deletions.
12 changes: 8 additions & 4 deletions cli/hzn.go
Original file line number Diff line number Diff line change
Expand Up @@ -801,17 +801,21 @@ Environment Variables:
smUserPw := smCmd.Flag("user-pw", msgPrinter.Sprintf("Horizon Exchange credentials to query secrets manager resources. The default is HZN_EXCHANGE_USER_AUTH environment variable. If you don't prepend it with the user's org, it will automatically be prepended with the value of the HZN_ORG_ID environment variable.")).Short('u').PlaceHolder("USER:PW").String()
smSecretCmd := smCmd.Command("secret", msgPrinter.Sprintf("List and manage secrets in the secrets manager."))
smSecretListCmd := smSecretCmd.Command("list | ls", msgPrinter.Sprintf("Display the names of the secrets in the secrets manager.")).Alias("ls").Alias("list")
smSecretListNodeId := smSecretListCmd.Flag("nodeId", msgPrinter.Sprintf("The node id of the node secret to list. If omit, the secret to list will be org or user secret")).Short('n').String()
smSecretListName := smSecretListCmd.Arg("secretName", msgPrinter.Sprintf("List just this one secret. Returns a boolean indicating the existence of the secret. This is the name of the secret used in the secrets manager. If the secret does not exist, returns with exit code 1.")).String()
smSecretAddCmd := smSecretCmd.Command("add", msgPrinter.Sprintf("Add a secret to the secrets manager."))
smSecretAddName := smSecretAddCmd.Arg("secretName", msgPrinter.Sprintf("The name of the secret. It must be unique within your organization. This name is used in deployment policies and patterns to bind this secret to a secret name in a service definition.")).Required().String()
smSecretAddNodeId := smSecretAddCmd.Flag("nodeId", msgPrinter.Sprintf("The node id that this secret will be applied to. If omit, the secret will be added as org or user secret")).Short('n').String()
smSecretAddFile := smSecretAddCmd.Flag("secretFile", msgPrinter.Sprintf("Filepath to a file containing the secret details. Mutually exclusive with --secretDetail. Specify -f- to read from stdin.")).Short('f').String()
smSecretAddKey := smSecretAddCmd.Flag("secretKey", msgPrinter.Sprintf("A key for the secret.")).Required().String()
smSecretAddDetail := smSecretAddCmd.Flag("secretDetail", msgPrinter.Sprintf("The secret details as a string. Secret details are the actual secret itself, not the name of the secret. For example, a password, a private key, etc. are examples of secret details. Mutually exclusive with --secretFile.")).Short('d').String()
smSecretAddOverwrite := smSecretAddCmd.Flag("overwrite", msgPrinter.Sprintf("Overwrite the existing secret if it exists in the secrets manager. It will skip the 'do you want to overwrite' prompt.")).Short('O').Bool()
smSecretRemoveCmd := smSecretCmd.Command("remove | rm", msgPrinter.Sprintf("Remove a secret in the secrets manager.")).Alias("rm").Alias("remove")
smSecretRemoveNodeId := smSecretRemoveCmd.Flag("nodeId", msgPrinter.Sprintf("The node id of the secret to be removed. If omit, the secret to be removed will be org or user secret")).Short('n').String()
smSecretRemoveForce := smSecretRemoveCmd.Flag("force", msgPrinter.Sprintf("Skip the 'are you sure?' prompt.")).Short('f').Bool()
smSecretRemoveName := smSecretRemoveCmd.Arg("secretName", msgPrinter.Sprintf("The name of the secret to be removed from the secrets manager.")).Required().String()
smSecretReadCmd := smSecretCmd.Command("read", msgPrinter.Sprintf("Read the details of a secret stored in the secrets manager. This consists of the key and value pair provided on secret creation."))
smSecretReadNodeId := smSecretReadCmd.Flag("nodeId", msgPrinter.Sprintf("The node id of the node secret to read. If omit, the secret to read will be org or user secret")).Short('n').String()
smSecretReadName := smSecretReadCmd.Arg("secretName", msgPrinter.Sprintf("The name of the secret to read in the secrets manager.")).Required().String()

versionCmd := app.Command("version", msgPrinter.Sprintf("Show the Horizon version.")) // using a cmd for this instead of --version flag, because kingpin takes over the latter and can't get version only when it is needed
Expand Down Expand Up @@ -1504,12 +1508,12 @@ Environment Variables:
fdo.VoucherDownload(*fdoOrg, *fdoUserPw, *fdoVoucherDownloadDevice, *fdoVoucherDownloadFile, *fdoVoucherDownloadOverwrite)

case smSecretListCmd.FullCommand():
secret_manager.SecretList(*smOrg, *smUserPw, *smSecretListName)
secret_manager.SecretList(*smOrg, *smUserPw, *smSecretListName, *smSecretListNodeId)
case smSecretAddCmd.FullCommand():
secret_manager.SecretAdd(*smOrg, *smUserPw, *smSecretAddName, *smSecretAddFile, *smSecretAddKey, *smSecretAddDetail, *smSecretAddOverwrite)
secret_manager.SecretAdd(*smOrg, *smUserPw, *smSecretAddName, *smSecretAddNodeId, *smSecretAddFile, *smSecretAddKey, *smSecretAddDetail, *smSecretAddOverwrite)
case smSecretRemoveCmd.FullCommand():
secret_manager.SecretRemove(*smOrg, *smUserPw, *smSecretRemoveName, *smSecretRemoveForce)
secret_manager.SecretRemove(*smOrg, *smUserPw, *smSecretRemoveName, *smSecretRemoveNodeId, *smSecretRemoveForce)
case smSecretReadCmd.FullCommand():
secret_manager.SecretRead(*smOrg, *smUserPw, *smSecretReadName)
secret_manager.SecretRead(*smOrg, *smUserPw, *smSecretReadName, *smSecretReadNodeId)
}
}
83 changes: 75 additions & 8 deletions cli/secrets_manager/secrets_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,16 @@ func queryWithRetry(query func() int, retryCount, retryInterval int) (httpCode i
return httpCode
}

// If secretName is empty, lists all the org level secrets and non-empty directories for the specified org in the secrets manager
// If secretName is specified, prints a json object indicating whether the given secret exists or not in the secrets manager for the org
// If the name provided is a directory, lists all the secrets in the directory.
func SecretList(org, credToUse, secretName string) {
// If secretName is empty
// - nodeId is empty: lists all the org level secrets and non-empty directories for the specified org in the secrets manager
// - else: list all the node secrets for the specified org
// If secretName is
// - nodeId is empty: prints a json object indicating whether the given secret exists or not in the secrets manager for the org
// - else: prints node secrets as a json object indicating whether the given secret exists or not
// If the name provided is a directory,
// - nodeId is empty: lists all the secrets in the directory
// - else: list all the node secrets for the given directory
func SecretList(org, credToUse, secretName, secretNodeId string) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()

Expand All @@ -75,9 +81,22 @@ func SecretList(org, credToUse, secretName string) {
secretName = secretName[:len(secretName)-1]
}

if !strings.Contains(secretName, "/node") && secretNodeId != "" {
// add /node/{nodeID} to the path
secretName = getSecretPathForNodeLevelSecret(secretName, secretNodeId)
}
// if given secretName := "", nodeId is specified, then secretName will be convert to "node/{nodeId}"
// if given secretName is a directory: "user/userdevadmin", nodeId is specified, then secretName will be converted to "user/{userId}/node/{nodeId}"
// if given secretName is "test-password", nodeId is specified, then secretname will be converted to "node/{nodeId}/test-password"
// if given secretName is "user/userdevadmin/test-password", nodeId is specified, then secretName will be converted to "user/{userId}/node/{nodeId}/test-password":

// query the agbot secure api
var resp []byte
listQuery := func() int {
// call LIST: org/secrets/node/{nodeId}
// org/secrets/user/{userId}/node/{nodeId}
// org/secrets/node/{nodeId}/test-password
// org/secrets/user/{userId}/node/{nodeId}/test-password
return cliutils.AgbotList("org"+cliutils.AddSlash(org)+"/secrets"+cliutils.AddSlash(secretName), cliutils.OrgAndCreds(org, credToUse),
[]int{200, 400, 401, 403, 404, 503, 504}, &resp)
}
Expand All @@ -89,12 +108,18 @@ func SecretList(org, credToUse, secretName string) {
isSecretDirectory := secretName == ""

// listing user secrets - user/<user>
// listing org node secrets - node/<nodeId>
// listing user node secrets - user/<user>/node/<nodeId>
if !isSecretDirectory {
nameParts := strings.Split(secretName, "/")
partsLength := len(nameParts)
isSecretDirectory = nameParts[0] == "user" && partsLength == 2
isUserSecretDirectory := nameParts[0] == "user" && (partsLength == 2 || partsLength == 4)
isOrgNodeSecretDirectory := nameParts[0] == "node" && partsLength == 2
isSecretDirectory = isUserSecretDirectory || isOrgNodeSecretDirectory
}

fmt.Printf("Lily - isSecretDirectory: %v\n", isSecretDirectory)

// parse and print the response
if retCode == 400 || retCode == 401 || retCode == 403 || retCode == 503 || retCode == 504 {
respString, _ := strconv.Unquote(string(resp))
Expand Down Expand Up @@ -134,7 +159,7 @@ func SecretList(org, credToUse, secretName string) {

// Adds or updates a secret in the secrets manager. Secret names are unique, if a secret already exists with the same name, the user
// will be prompted if they want to overwrite the existing secret, unless the secretOverwrite flag is set
func SecretAdd(org, credToUse, secretName, secretFile, secretKey, secretDetail string, secretOverwrite bool) {
func SecretAdd(org, credToUse, secretName, secretNodeId, secretFile, secretKey, secretDetail string, secretOverwrite bool) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()

Expand All @@ -143,6 +168,12 @@ func SecretAdd(org, credToUse, secretName, secretFile, secretKey, secretDetail s
secretName = secretName[:len(secretName)-1]
}

// org: test-password, user secret: user/userdevadmin/test-password
if !strings.Contains(secretName, "/node") && secretNodeId != "" {
// add /node/{nodeID} to the path
secretName = getSecretPathForNodeLevelSecret(secretName, secretNodeId)
}

// check the input
if secretFile != "" && secretDetail != "" {
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("-f is mutually exclusive with --secretDetail."))
Expand All @@ -151,6 +182,9 @@ func SecretAdd(org, credToUse, secretName, secretFile, secretKey, secretDetail s
cliutils.Fatal(cliutils.CLI_INPUT_ERROR, msgPrinter.Sprintf("Must specify either -f or --secretDetail."))
}

// eg:
// org secret: hzn sm secret add --secretKey password -d password321 -O -o userdev -u userdevadmin:userdevadminpw test-password
// user secret: hzn sm secret add --secretKey password -d password123 -o userdev -u userdevadmin:userdevadminpw user/userdevadmin/test-password
// check if the secret already exists by querying the api
secretExists := false
var resp []byte
Expand Down Expand Up @@ -222,7 +256,7 @@ func SecretAdd(org, credToUse, secretName, secretFile, secretKey, secretDetail s
}

// Removes a secret in the secrets manager. If the secret does not exist, an error (fatal) is raised
func SecretRemove(org, credToUse, secretName string, forceRemoval bool) {
func SecretRemove(org, credToUse, secretName, secretNodeId string, forceRemoval bool) {
// get message printer
msgPrinter := i18n.GetMessagePrinter()

Expand All @@ -231,6 +265,12 @@ func SecretRemove(org, credToUse, secretName string, forceRemoval bool) {
secretName = secretName[:len(secretName)-1]
}

// org: test-password, user secret: user/userdevadmin/test-password
if !strings.Contains(secretName, "/node") && secretNodeId != "" {
// add /node/{nodeID} to the path
secretName = getSecretPathForNodeLevelSecret(secretName, secretNodeId)
}

// confirm secret removal
if !forceRemoval {
cliutils.ConfirmRemove(msgPrinter.Sprintf("Are you sure you want to remove secret %s from the secrets manager?", secretName))
Expand Down Expand Up @@ -260,12 +300,17 @@ func SecretRemove(org, credToUse, secretName string, forceRemoval bool) {
}

// Pulls secret details from the secrets manager. If the secret does not exist, an error (fatal) is raised
func SecretRead(org, credToUse, secretName string) {
func SecretRead(org, credToUse, secretName, secretNodeId string) {
// get rid of trailing / from secret name
if strings.HasSuffix(secretName, "/") {
secretName = secretName[:len(secretName)-1]
}

if !strings.Contains(secretName, "/node") && secretNodeId != "" {
// add /node/{nodeID} to the path
secretName = getSecretPathForNodeLevelSecret(secretName, secretNodeId)
}

// query the agbot secure api
var resp []byte
listQuery := func() int {
Expand All @@ -287,3 +332,25 @@ func SecretRead(org, credToUse, secretName string) {
}

}

func getSecretPathForNodeLevelSecret(secretName string, secretNodeId string) string {
secretName = strings.TrimSpace(secretName)
if secretName == "" {
secretName = fmt.Sprintf("node/%v", secretNodeId)
} else {
parts := strings.Split(secretName, "/")
if len(parts) == 1 {
// org level, secretName: "test-password"
secretName = fmt.Sprintf("node/%v/%v", secretNodeId, secretName)
} else if len(parts) == 2 {
// list secret given a directory, eg: user/userdevadmin
secretName = fmt.Sprintf("%v/node/%v", secretName, secretNodeId)
} else if len(parts) == 3 {
// user level
// parts: [user, ${userid}, test-password]
secretName = fmt.Sprintf("%v/%v/node/%v/%v", parts[0], parts[1], secretNodeId, parts[2])
}
}

return secretName
}

0 comments on commit 7cfb050

Please sign in to comment.