mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 03:27:03 -07:00
feat: added sessions cmd
This commit is contained in:
parent
a6c445b86f
commit
fdf715c6b3
2 changed files with 357 additions and 20 deletions
|
|
@ -25,7 +25,7 @@ var secretsCmd = &cobra.Command{
|
||||||
// generate new key and set environment variable
|
// generate new key and set environment variable
|
||||||
export MASTER_KEY=$(magellan secrets generatekey)
|
export MASTER_KEY=$(magellan secrets generatekey)
|
||||||
|
|
||||||
// store specific BMC node creds for collect and crawl in default secrets store (--file/-f flag not set)
|
// store specific BMC node creds for collect and crawl in default secrets store (--secrets-file/-f flag not set)
|
||||||
magellan secrets store $bmc_host $bmc_creds
|
magellan secrets store $bmc_host $bmc_creds
|
||||||
|
|
||||||
// retrieve creds from secrets store
|
// retrieve creds from secrets store
|
||||||
|
|
@ -117,8 +117,8 @@ var secretsStoreCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the decoded string if it's a valid JSON and has creds
|
// check the decoded string if it's a valid JSON and has creds
|
||||||
if !isValidCredsJSON(string(decoded)) {
|
if !json.Valid(decoded) {
|
||||||
log.Error().Err(err).Msg("value is not a valid JSON or is missing credentials")
|
log.Error().Err(err).Msg("decoded secret value is not a valid JSON")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,8 +144,9 @@ var secretsStoreCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we have valid JSON with "username" and "password" properties
|
// make sure we have valid JSON with "username" and "password" properties
|
||||||
if !isValidCredsJSON(string(secretValue)) {
|
|
||||||
log.Error().Err(err).Msg("not a valid JSON or creds")
|
if !json.Valid([]byte(secretValue)) {
|
||||||
|
log.Error().Err(err).Msg("secret value is not a valid JSON")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
store, err = secrets.OpenStore(secretsFile)
|
store, err = secrets.OpenStore(secretsFile)
|
||||||
|
|
@ -165,21 +166,6 @@ var secretsStoreCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidCredsJSON(val string) bool {
|
|
||||||
var (
|
|
||||||
valid = !json.Valid([]byte(val))
|
|
||||||
creds map[string]string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
err = json.Unmarshal([]byte(val), &creds)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, valid = creds["username"]
|
|
||||||
_, valid = creds["password"]
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
|
|
||||||
var secretsRetrieveCmd = &cobra.Command{
|
var secretsRetrieveCmd = &cobra.Command{
|
||||||
Use: "retrieve secretID",
|
Use: "retrieve secretID",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
|
|
||||||
351
cmd/sessions.go
Normal file
351
cmd/sessions.go
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/davidallendj/magellan/internal/util"
|
||||||
|
"github.com/davidallendj/magellan/pkg/secrets"
|
||||||
|
"github.com/davidallendj/magellan/pkg/sessions"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/stmcginnis/gofish/redfish"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
sessionID string
|
||||||
|
sessionToken string
|
||||||
|
sessionTokenPath string
|
||||||
|
storeToken bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var sessionCmd = &cobra.Command{
|
||||||
|
Use: "sessions",
|
||||||
|
Example: ` # use BMC credentials to get session token
|
||||||
|
magellan sessions login $bmc_host -u $bmc_username -p $bmc_password
|
||||||
|
|
||||||
|
# show active available sessions
|
||||||
|
magellan sessions
|
||||||
|
magellan sessions list
|
||||||
|
|
||||||
|
# show session tokens in secrets store using host
|
||||||
|
magellan secrets retrieve $bmc_host
|
||||||
|
magellan secrets list
|
||||||
|
|
||||||
|
# delete an active session (requires a token)
|
||||||
|
magellan sessions delete --session-id $SESSION_ID --session-token $SESSION_TOKEN
|
||||||
|
|
||||||
|
# delete an active session (token stored in secrets store)
|
||||||
|
magellan sessions delete --session-id $SESSION_ID --secrets-file secrets.json`,
|
||||||
|
Short: "Manage sessions with BMCs",
|
||||||
|
Long: "Manage sessions with BMCs. Session tokens can be stored in the secrets store if the MASTER_KEY environment variable is set.",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) == 0 {
|
||||||
|
cmd.Help()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionLoginCmd = &cobra.Command{
|
||||||
|
Use: "login [hosts...]",
|
||||||
|
Example: ` // generate the MASTER_KEY for the secret store
|
||||||
|
export MASTER_KEY=(magellan secrets generatekey)
|
||||||
|
|
||||||
|
// create a new session by logging into BMC with creds
|
||||||
|
magellan sessions login https://bmc_host -u $bmc_username $bmc_password`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Short: "Log into a BMC for a session token.",
|
||||||
|
Long: "",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// require the MASTER_KEY and secret store to store creds
|
||||||
|
var masterKey = os.Getenv("MASTER_KEY")
|
||||||
|
if !validSecretArgs(masterKey, secretsFile) {
|
||||||
|
log.Error().Str("key", masterKey).Str("secrets-file", secretsFile).Msg("requires MASTER_KEY environment variable to be set with a secrets store")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSessions := []*redfish.Session{}
|
||||||
|
for _, host := range args {
|
||||||
|
// log into the BMC to create new session
|
||||||
|
store := initSecretsStore(host)
|
||||||
|
session, sessionToken, err := sessions.Login(host, store)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to get session token")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sessionToken != "" {
|
||||||
|
log.Debug().
|
||||||
|
Str("session-token", sessionToken).
|
||||||
|
Str("host", host).
|
||||||
|
Msg("got session token successfully")
|
||||||
|
} else {
|
||||||
|
log.Warn().
|
||||||
|
Str("host", host).
|
||||||
|
Msg("no session token retrieved")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newSessions = append(newSessions, session)
|
||||||
|
|
||||||
|
// get the currently existing secrets to update for host
|
||||||
|
secret, err := store.GetSecretByID(host)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("session-id", session.ID).Msg("could not get secret for host")
|
||||||
|
|
||||||
|
// no secret found so create a new one and continue
|
||||||
|
newSecret, err := json.Marshal(map[string]any{
|
||||||
|
"session-tokens": map[string]string{
|
||||||
|
session.ID: sessionToken,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
err = store.StoreSecretByID(host, string(newSecret))
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("secret_id", host).Msg("failed to store secret by ID")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// secret should be JSON so try and unmarshal and update
|
||||||
|
updated, err := util.UpdateJSON([]byte(secret), "", util.JSONObject{})
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("host", host).Msg("failed to update secret")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the session ID in secrets store for the host
|
||||||
|
err = store.StoreSecretByID(host, string(updated))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("host", host).Msg("could not store the updated secret")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newSessions) == 0 {
|
||||||
|
log.Warn().Msg("no new sessions created")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the session IDs for created sessions
|
||||||
|
switch format {
|
||||||
|
case "list":
|
||||||
|
for _, session := range newSessions {
|
||||||
|
fmt.Println(session.ID)
|
||||||
|
}
|
||||||
|
case "json":
|
||||||
|
util.PrintJSON(newSessions)
|
||||||
|
case "yaml":
|
||||||
|
util.PrintYAML(newSessions)
|
||||||
|
default:
|
||||||
|
log.Error().Msg("unrecognized output format")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionLogoutCmd = &cobra.Command{
|
||||||
|
Use: "logout",
|
||||||
|
Example: ` // logout of session using a session token
|
||||||
|
magellan sessions logout https://172.21.0.2 --session-id 2JFKD
|
||||||
|
|
||||||
|
// log out of session using BMC credentails
|
||||||
|
magellan session logout https://172.21.0.2 --session-id 2JFKD -u $bmc_username -p $bmc_password`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Short: "Log out of an active session",
|
||||||
|
Long: "Log out of an active session. Session tokens will always be used first if the SESSION_TOKEN environment variable is set.",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
for _, host := range args {
|
||||||
|
// try to log out with session token if set
|
||||||
|
// var err error
|
||||||
|
// sessionToken, err = auth.LoadSessionToken(sessionTokenPath)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Warn().Err(err).Str("host", host).Msg("could not load session token from file")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// err := sessions.LogoutWithToken(host, sessionID, sessionToken, insecure)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Warn().Err(err).Str("host", host).Msg("could not log out of session with token...trying with credentials")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// log out of the sessions for specified host
|
||||||
|
store := initSecretsStore(host)
|
||||||
|
err := sessions.Logout(host, sessionID, store, insecure)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("host", host).Msg("failed to log out of session")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove the session token from the secret store
|
||||||
|
masterKey := os.Getenv("MASTER_KEY")
|
||||||
|
if validSecretArgs(masterKey, secretsFile) {
|
||||||
|
store, err := secrets.OpenStore(secretsFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("host", host).Msg("failed to open secret store to remove session token")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the secrets for the specified host
|
||||||
|
secretValue, err := store.GetSecretByID(host)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("host", host).Msg("failed to get secret for host")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshal creds into a map to remove session token
|
||||||
|
creds := map[string]any{}
|
||||||
|
err = json.Unmarshal([]byte(secretValue), &creds)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("host", host).Msg("failed to unmarshal secret")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove only the specific session ID and tokens
|
||||||
|
sessions, ok := creds["sessions"]
|
||||||
|
if ok {
|
||||||
|
switch sessions.(type) {
|
||||||
|
case map[string]string:
|
||||||
|
delete(sessions.(map[string]string), sessionID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the secrets by storing again
|
||||||
|
creds["sessions"] = sessions
|
||||||
|
newCreds, err := json.Marshal(creds)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("host", host).Msg("failed to marshal new secrets")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
store.StoreSecretByID(host, string(newCreds))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionStatusCmd = &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
|
Example: ` // show the host's session service status in YAML format
|
||||||
|
magellan sessions status https://172.21.0.2:5000 -u $bmc_username -p $bmc_password -i -F yaml`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Short: "Show the status of the session service",
|
||||||
|
Long: "",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
for _, host := range args {
|
||||||
|
store := initSecretsStore(host)
|
||||||
|
status, err := sessions.GetServiceStatus(host, store, insecure)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("host", host).Msg("failed to get session service status")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show the service status in given format
|
||||||
|
switch format {
|
||||||
|
case FORMAT_LIST:
|
||||||
|
fmt.Printf("%s: %v (%d secs)", host, status.Enabled, status.Timeout)
|
||||||
|
case FORMAT_JSON:
|
||||||
|
util.PrintJSON(util.JSONObject{host: status})
|
||||||
|
case FORMAT_YAML:
|
||||||
|
util.PrintYAML(util.JSONObject{host: status})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Example: ` # show active sessions and tokens
|
||||||
|
magellan sessions list`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Short: "Show a list of active session and tokens",
|
||||||
|
Long: "",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// show all of the session IDs then exit
|
||||||
|
for _, host := range args {
|
||||||
|
store := initSecretsStore(host)
|
||||||
|
if sessionID != "" {
|
||||||
|
session, err := sessions.GetSession(sessionID, host, store, insecure)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("host", host).
|
||||||
|
Str("session-id", sessionID).
|
||||||
|
Msg("failed to get session with ID for host")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case FORMAT_LIST:
|
||||||
|
fallthrough
|
||||||
|
case FORMAT_JSON:
|
||||||
|
util.PrintJSON(session)
|
||||||
|
case FORMAT_YAML:
|
||||||
|
util.PrintYAML(session)
|
||||||
|
default:
|
||||||
|
log.Error().Msg("unrecognized output format")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sessionIDs, err := sessions.GetSessionIDs(host, store, insecure)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("host", host).
|
||||||
|
Msg("failed to get session ID for host")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(sessionIDs) == 0 {
|
||||||
|
log.Warn().
|
||||||
|
Str("host", host).
|
||||||
|
Msg("no session IDs found for host")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case FORMAT_LIST:
|
||||||
|
fmt.Println(strings.Join(sessionIDs, "\n"))
|
||||||
|
case FORMAT_JSON:
|
||||||
|
util.PrintJSON(sessionIDs)
|
||||||
|
case FORMAT_YAML:
|
||||||
|
util.PrintYAML(sessionIDs)
|
||||||
|
default:
|
||||||
|
log.Error().Msg("unrecognized output format")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
// sessionLoginCmd.Flags().BoolVarP(&insecure, "insecure", "i", false, "Skip TLS verification")
|
||||||
|
|
||||||
|
sessionLoginCmd.Aliases = append(sessionLoginCmd.Aliases, "new", "create", "add")
|
||||||
|
|
||||||
|
sessionLogoutCmd.Flags().StringVar(&sessionID, "session-id", "", "Set the session ID to end session")
|
||||||
|
// sessionLogoutCmd.Flags().StringVar(&sessionToken, "session-token", "", "Set the session token used to log out of session (can also be set with the SESSION_TOKEN environment variable)")
|
||||||
|
|
||||||
|
sessionLogoutCmd.MarkFlagRequired("session-id")
|
||||||
|
sessionLogoutCmd.Aliases = append(sessionCmd.Aliases, "delete", "remove")
|
||||||
|
|
||||||
|
sessionListCmd.Flags().StringVar(&sessionID, "session-id", "", "Show more information for specified session")
|
||||||
|
sessionListCmd.Flags().StringVar(&secretsFile, "secrets-file", "secrets.json", "Set the path to secrets store file to store credentials")
|
||||||
|
|
||||||
|
sessionCmd.PersistentFlags().BoolVarP(&insecure, "insecure", "i", false, "Skip TLS verification")
|
||||||
|
sessionCmd.PersistentFlags().StringVarP(&username, "username", "u", "", "Set the username for BMC login")
|
||||||
|
sessionCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "Set the password for BMC login")
|
||||||
|
sessionCmd.PersistentFlags().StringVar(&sessionTokenPath, "session-token-file", "", "Set the session token from a file")
|
||||||
|
sessionCmd.PersistentFlags().StringVar(&sessionToken, "session-token", "", "Set the session token")
|
||||||
|
sessionCmd.PersistentFlags().StringVarP(&format, "format", "F", "list", "Set the output format (list|json|yaml)")
|
||||||
|
sessionCmd.PersistentFlags().StringVarP(&secretsFile, "secrets-file", "f", "secrets.json", "Set the path to secrets store file to store credentials")
|
||||||
|
|
||||||
|
sessionCmd.MarkFlagRequired("username")
|
||||||
|
sessionCmd.MarkFlagRequired("password")
|
||||||
|
sessionCmd.MarkFlagsRequiredTogether("username", "password")
|
||||||
|
sessionCmd.MarkFlagsMutuallyExclusive("session-token", "session-token-file")
|
||||||
|
|
||||||
|
sessionCmd.AddCommand(sessionLoginCmd, sessionLogoutCmd, sessionListCmd, sessionStatusCmd)
|
||||||
|
rootCmd.AddCommand(sessionCmd)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue