mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 11:37:01 -07:00
feat: sessions core implementation
This commit is contained in:
parent
5e7330d5e7
commit
49f563a1d3
1 changed files with 286 additions and 0 deletions
286
pkg/sessions/sessions.go
Normal file
286
pkg/sessions/sessions.go
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/davidallendj/magellan/internal/util"
|
||||
"github.com/davidallendj/magellan/pkg/bmc"
|
||||
"github.com/davidallendj/magellan/pkg/client"
|
||||
"github.com/davidallendj/magellan/pkg/secrets"
|
||||
"github.com/stmcginnis/gofish"
|
||||
"github.com/stmcginnis/gofish/redfish"
|
||||
)
|
||||
|
||||
type Params struct {
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
SessionID string
|
||||
SessionToken string
|
||||
}
|
||||
|
||||
type ServiceStatus struct {
|
||||
Enabled bool
|
||||
Timeout int
|
||||
}
|
||||
|
||||
// Login() makes a single POST to the /redfish/v1/SessionService/Session endpoint to obtain a session token that can used for subsequent calls to the BMC. Using the SessionService reduces the overhead associated with logging into the BMC.
|
||||
//
|
||||
// Returns session ID, session token (X-AUTH-TOKEN), and/or error.
|
||||
func Login(host string, store secrets.SecretStore) (*redfish.Session, string, error) {
|
||||
var (
|
||||
c client.DefaultClient
|
||||
body client.HTTPBody
|
||||
res *http.Response
|
||||
encoded string
|
||||
err error
|
||||
)
|
||||
|
||||
creds, err := loadBMCCreds(host, store)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load BMC credentials: %v", err)
|
||||
}
|
||||
|
||||
encoded = base64.StdEncoding.EncodeToString(fmt.Appendf([]byte{}, "%s:%s", creds.Username, creds.Password))
|
||||
res, body, err = client.MakeRequest(c.Client,
|
||||
host+"/redfish/v1/SessionService/Sessions",
|
||||
http.MethodPost,
|
||||
client.HTTPBody(fmt.Sprintf("{\"UserName\": \"%s\", \"Password\": \"%s\"}", creds.Username, creds.Password)),
|
||||
client.HTTPHeader{
|
||||
"Authorization": fmt.Sprintf("Basic %s", encoded),
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to make request to session service: %v", err)
|
||||
}
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return nil, "", fmt.Errorf("response returned status code %d", res.StatusCode)
|
||||
}
|
||||
|
||||
// extract the session endpoint from body if JSON
|
||||
obj, err := util.FromJSON(body)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to unmarshal JSON from session login response: %v", err)
|
||||
}
|
||||
|
||||
// make a request to endpoint to get session data
|
||||
endpoint, ok := obj["message"]
|
||||
session := &redfish.Session{}
|
||||
if ok {
|
||||
switch endpoint.(type) {
|
||||
case string:
|
||||
_, body, err := client.MakeRequest(c.Client, host+endpoint.(string), http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to make request for session token for host '%s': %v", host, err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &session)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to unmarshal response from session request for host '%s': %v", host, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, "", fmt.Errorf("failed to extract session token from response JSON")
|
||||
}
|
||||
|
||||
// extract the X-AUTH-TOKEN from the response header
|
||||
return session, res.Header.Get("X-Auth-Token"), nil
|
||||
}
|
||||
|
||||
func Logout(host string, sessionID string, store secrets.SecretStore, insecure bool) error {
|
||||
creds, err := loadBMCCreds(host, store)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load BMC credentials: %v", err)
|
||||
}
|
||||
|
||||
// initialize gofish client
|
||||
client, err := gofish.Connect(gofish.ClientConfig{
|
||||
Endpoint: host,
|
||||
Insecure: insecure,
|
||||
Username: creds.Username,
|
||||
Password: creds.Password,
|
||||
BasicAuth: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to BMC: %v", err)
|
||||
}
|
||||
defer client.Logout()
|
||||
|
||||
res, err := client.Delete("/redfish/v1/SessionService/Sessions/" + sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make request to delete session: %v", err)
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("response to delete session returned code %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LogoutWithToken(host string, sessionID string, sessionToken string, insecure bool) error {
|
||||
// initialize gofish client
|
||||
client, err := gofish.Connect(gofish.ClientConfig{
|
||||
Endpoint: host,
|
||||
Insecure: insecure,
|
||||
BasicAuth: false,
|
||||
Session: &gofish.Session{
|
||||
ID: sessionID,
|
||||
Token: sessionToken,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to BMC: %v", err)
|
||||
}
|
||||
defer client.Logout()
|
||||
client.Delete(host + "/redfish/v1/SessionService/Sessions/" + sessionID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetServiceStatus(host string, store secrets.SecretStore, insecure bool) (*ServiceStatus, error) {
|
||||
creds, err := loadBMCCreds(host, store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load BMC credentials: %v", err)
|
||||
}
|
||||
// initialize gofish client
|
||||
client, err := gofish.Connect(gofish.ClientConfig{
|
||||
Endpoint: host,
|
||||
Username: creds.Username,
|
||||
Password: creds.Password,
|
||||
Insecure: insecure,
|
||||
BasicAuth: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to BMC: %v", err)
|
||||
}
|
||||
defer client.Logout()
|
||||
sessionService, err := client.GetService().SessionService()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get session service: %v", err)
|
||||
}
|
||||
|
||||
return &ServiceStatus{
|
||||
Enabled: sessionService.ServiceEnabled,
|
||||
Timeout: sessionService.SessionTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetSessionIDs(host string, store secrets.SecretStore, insecure bool) ([]string, error) {
|
||||
|
||||
sessions, err := getSessions(host, store, insecure)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the active session IDs from the session service: %v", err)
|
||||
}
|
||||
|
||||
sessionIDs := []string{}
|
||||
for _, session := range sessions {
|
||||
sessionIDs = append(sessionIDs, session.ID)
|
||||
}
|
||||
|
||||
return sessionIDs, nil
|
||||
}
|
||||
|
||||
func GetSession(id string, host string, store secrets.SecretStore, insecure bool) (*redfish.Session, error) {
|
||||
// get all of the sessions from the BMC node
|
||||
sessions, err := getSessions(host, store, insecure)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the active session IDs from the session service: %v", err)
|
||||
}
|
||||
|
||||
// now, only return the one that we want using the session ID
|
||||
index := slices.IndexFunc(sessions, func(item *redfish.Session) bool {
|
||||
return id == item.ID
|
||||
})
|
||||
if index >= 0 {
|
||||
return sessions[index], nil
|
||||
}
|
||||
|
||||
// we didn't find anything so return error
|
||||
return nil, fmt.Errorf("could not find session")
|
||||
}
|
||||
|
||||
func getSessions(host string, store secrets.SecretStore, insecure bool) ([]*redfish.Session, error) {
|
||||
creds, err := loadBMCCreds(host, store)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load BMC credentials: %v", err)
|
||||
}
|
||||
|
||||
// initialize gofish client
|
||||
client, err := gofish.Connect(gofish.ClientConfig{
|
||||
Endpoint: host,
|
||||
Username: creds.Username,
|
||||
Password: creds.Password,
|
||||
Insecure: insecure,
|
||||
BasicAuth: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to BMC: %v", err)
|
||||
}
|
||||
defer client.Logout()
|
||||
|
||||
service, err := client.Service.SessionService()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the session service: %v", err)
|
||||
}
|
||||
|
||||
return service.Sessions()
|
||||
}
|
||||
|
||||
// SessionLogin() makes a single POST to the /redfish/v1/SessionService/Session using the `gofish` library which can be used for subsequent requests to the BMC.
|
||||
//
|
||||
// NOTE: This implementation makes multiple requests to the BMC to obtain the need session token whereas the other implementation makes a single request to the required endpoint.
|
||||
// func SessionLogin(config crawler.CrawlerConfig) (string, error) {
|
||||
// // get username and password from secret store
|
||||
// bmc_creds, err := crawler.LoadBMCCreds(config)
|
||||
// if err != nil {
|
||||
// event := log.Error()
|
||||
// event.Err(err)
|
||||
// event.Msg("failed to load BMC credentials")
|
||||
// return "", err
|
||||
// }
|
||||
|
||||
// // initialize gofish client
|
||||
// client, err := gofish.Connect(gofish.ClientConfig{
|
||||
// Endpoint: config.URI,
|
||||
// Username: bmc_creds.Username,
|
||||
// Password: bmc_creds.Password,
|
||||
// Insecure: config.Insecure,
|
||||
// BasicAuth: true,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("failed to connect to BMC: %v")
|
||||
// }
|
||||
|
||||
// service, err := client.Service.SessionService()
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("failed to get the session service: %v", err)
|
||||
// }
|
||||
|
||||
// res, err := service.PostWithResponse(uri string, map[string]any{
|
||||
// "UserName": bmc_creds.Username,
|
||||
// "Password": bmc_creds.Password,
|
||||
// })
|
||||
|
||||
// // extract the session token from headers
|
||||
// }
|
||||
|
||||
func loadBMCCreds(host string, store secrets.SecretStore) (bmc.BMCCredentials, error) {
|
||||
var (
|
||||
creds bmc.BMCCredentials
|
||||
secretValue string
|
||||
err error
|
||||
)
|
||||
// get creds from secret store
|
||||
secretValue, err = store.GetSecretByID(host)
|
||||
if err != nil {
|
||||
return creds, fmt.Errorf("failed to get secret by ID: %v", err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(secretValue), &creds)
|
||||
if err != nil {
|
||||
return creds, fmt.Errorf("failed to unmarshal credentials: %v", err)
|
||||
}
|
||||
return creds, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue