Skip to content

Redfish useraccounts #242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ With approval from [Booking.com](http://www.booking.com), the code and
specification were generalized and published as Open Source on github, for
which the authors would like to express their gratitude.

bmclib interfaces with Redfish with https://github.com/stmcginnis/gofish

#### Authors
- Juliano Martinez
- Joel Rebello
- Joel Rebello
- Guilherme M. Schroeder
- Mariano Guezuraga
21 changes: 21 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ var (

// ErrDeviceNotMatched is the error returned when the device was not a type it was probed for
ErrDeviceNotMatched = errors.New("the vendor device did not match the probe")

// ErrRetrievingUserAccounts is returned when bmclib is unable to retrieve user accounts from the BMC
ErrRetrievingUserAccounts = errors.New("error retrieving user accounts")

// ErrInvalidUserRole is returned when the given user account role is not valid
ErrInvalidUserRole = errors.New("invalid user account role")

// ErrUserParamsRequired is returned when all the required user parameters are not provided - username, password, role
ErrUserParamsRequired = errors.New("username, password and role are required parameters")

// ErrUserAccountExists is returned when a user account with the username is already present
ErrUserAccountExists = errors.New("user account already exists")

// ErrNoUserSlotsAvailable is returned when there are no user account slots available
ErrNoUserSlotsAvailable = errors.New("no user account slots available")

// ErrUserAccountNotFound is returned when the user account is not present
ErrUserAccountNotFound = errors.New("given user account does not exist")

// ErrUserAccountUpdate is returned when the user account failed to be updated
ErrUserAccountUpdate = errors.New("user account attributes could not be updated")
)

type ErrUnsupportedHardware struct {
Expand Down
48 changes: 48 additions & 0 deletions examples/v1/users/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"context"
"log"
"time"

"github.com/bmc-toolbox/bmclib"
"github.com/bombsimon/logrusr"
"github.com/sirupsen/logrus"
)

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

// set BMC parameters here
host := ""
port := ""
user := ""
pass := ""

l := logrus.New()
l.Level = logrus.DebugLevel
logger := logrusr.NewLogger(l)

if host == "" || user == "" || pass == "" {
log.Fatal("required host/user/pass parameters not defined")
}

cl := bmclib.NewClient(host, port, user, pass, bmclib.WithLogger(logger))

cl.Registry.Drivers = cl.Registry.Using("redfish")
// cl.Registry.Drivers = cl.Registry.Using("vendorapi")

err := cl.Open(ctx)
if err != nil {
log.Fatal(err, "bmc login failed")
}

defer cl.Close(ctx)

_, err = cl.CreateUser(ctx, "foobar", "sekurity101", "Administrator")
if err != nil {
l.Error(err)
}

}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1
github.com/stmcginnis/gofish v0.8.0
github.com/stmcginnis/gofish v0.12.0
github.com/stretchr/testify v1.7.0
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stmcginnis/gofish v0.8.0 h1:lGMKmyHQr8rG6SHKL9SU8BuA4bV/r3Pj/8J+DkuT0gY=
github.com/stmcginnis/gofish v0.8.0/go.mod h1:BGtQsY16q48M2K6KDAs38QXtNoHrkXaY/WZ/mmyMgNc=
github.com/stmcginnis/gofish v0.12.0 h1:6UbNePjA++XkHtCKKLr7envKENxljJ1YyD8f4vS3Zeo=
github.com/stmcginnis/gofish v0.12.0/go.mod h1:BGtQsY16q48M2K6KDAs38QXtNoHrkXaY/WZ/mmyMgNc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand Down
9 changes: 9 additions & 0 deletions internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ func ValidateUserConfig(cfgUsers []*cfgresources.User) (err error) {

return nil
}

func StringInSlice(str string, sl []string) bool {
for _, s := range sl {
if str == s {
return true
}
}
return false
}
63 changes: 53 additions & 10 deletions providers/asrockrack/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,49 @@ type biosUpdateAction struct {
Action int `json:"action"`
}

func (a *ASRockRack) listUsers() ([]*UserAccount, error) {
endpoint := "api/settings/users"

resp, statusCode, err := a.queryHTTPS(endpoint, "GET", nil, nil, 0)
if err != nil {
return nil, err
}

if statusCode != http.StatusOK {
return nil, fmt.Errorf("non 200 response: %d", statusCode)
}

accounts := []*UserAccount{}

err = json.Unmarshal(resp, &accounts)
if err != nil {
return nil, err
}

return accounts, nil
}

func (a *ASRockRack) createUpdateUser(account *UserAccount) error {
endpoint := "api/settings/users/" + fmt.Sprintf("%d", account.ID)

payload, err := json.Marshal(account)
if err != nil {
return err
}

headers := map[string]string{"Content-Type": "application/json"}
_, statusCode, err := a.queryHTTPS(endpoint, "PUT", bytes.NewReader(payload), headers, 0)
if err != nil {
return err
}

if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

return nil
}

// 1 Set BMC to flash mode and prepare flash area
// at this point all logged in sessions are terminated
// and no logins are permitted
Expand All @@ -70,7 +113,7 @@ func (a *ASRockRack) setFlashMode() error {
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand Down Expand Up @@ -132,7 +175,7 @@ func (a *ASRockRack) uploadFirmware(endpoint string, fwReader io.Reader, fileSiz
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand All @@ -148,7 +191,7 @@ func (a *ASRockRack) verifyUploadedFirmware() error {
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand All @@ -172,7 +215,7 @@ func (a *ASRockRack) upgradeBMC() error {
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand All @@ -188,7 +231,7 @@ func (a *ASRockRack) reset() error {
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand All @@ -202,7 +245,7 @@ func (a *ASRockRack) flashProgress(endpoint string) (*upgradeProgress, error) {
return nil, err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return nil, fmt.Errorf("non 200 response: %d", statusCode)
}

Expand All @@ -224,7 +267,7 @@ func (a *ASRockRack) firmwareInfo() (*firmwareInfo, error) {
return nil, err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return nil, fmt.Errorf("non 200 response: %d", statusCode)
}

Expand Down Expand Up @@ -255,7 +298,7 @@ func (a *ASRockRack) biosUpgradeConfiguration() error {
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand Down Expand Up @@ -285,7 +328,7 @@ func (a *ASRockRack) biosUpgrade() error {
return err
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response: %d", statusCode)
}

Expand Down Expand Up @@ -343,7 +386,7 @@ func (a *ASRockRack) httpsLogout() error {
return fmt.Errorf("Error logging out: " + err.Error())
}

if statusCode != 200 {
if statusCode != http.StatusOK {
return fmt.Errorf("non 200 response at https logout: %d", statusCode)
}

Expand Down
22 changes: 19 additions & 3 deletions providers/asrockrack/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package asrockrack
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
Expand All @@ -25,6 +24,9 @@ var (
fwUploadResponse = []byte(`{"cc": 0}`)
fwVerificationResponse = []byte(`[ { "id": 1, "current_image_name": "ast2500e", "current_image_version1": "0.01.00", "current_image_version2": "", "new_image_version": "0.03.00", "section_status": 0, "verification_status": 5 } ]`)
fwUpgradeProgress = []byte(`{ "id": 1, "action": "Flashing...", "progress": "__PERCENT__% done ", "state": __STATE__ }`)
usersPayload = []byte(`[ { "id": 1, "name": "anonymous", "access": 0, "kvm": 1, "vmedia": 1, "snmp": 0, "prev_snmp": 0, "network_privilege": "administrator", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "none", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "ami_format", "ssh_key": "Not Available", "creation_time": 4802 }, { "id": 2, "name": "admin", "access": 1, "kvm": 1, "vmedia": 1, "snmp": 0, "prev_snmp": 0, "network_privilege": "administrator", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "none", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "ami_format", "ssh_key": "Not Available", "creation_time": 188 }, { "id": 3, "name": "foo", "access": 1, "kvm": 1, "vmedia": 1, "snmp": 0, "prev_snmp": 0, "network_privilege": "administrator", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "none", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "ami_format", "ssh_key": "Not Available", "creation_time": 4802 }, { "id": 4, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 5, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 6, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 7, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 8, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 9, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 }, { "id": 10, "name": "", "access": 0, "kvm": 0, "vmedia": 0, "snmp": 0, "prev_snmp": 0, "network_privilege": "", "fixed_user_count": 2, "snmp_access": "", "OEMProprietary_level_Privilege": 1, "privilege_limit_serial": "", "snmp_authentication_protocol": "", "snmp_privacy_protocol": "", "email_id": "", "email_format": "", "ssh_key": "Not Available", "creation_time": 0 } ]`)
// TODO: implement under rw mutex
httpRequestTestVar *http.Request
)

// setup test BMC
Expand Down Expand Up @@ -80,6 +82,9 @@ func mockASRockBMC() *httptest.Server {
handler.HandleFunc("/api/maintenance/reset", bmcFirmwareUpgrade)
handler.HandleFunc("/api/asrr/maintenance/BIOS/firmware", biosFirmwareUpgrade)

// user accounts endpoints
handler.HandleFunc("/api/settings/users", userAccountList)
handler.HandleFunc("/api/settings/users/3", userAccountList)
return httptest.NewTLSServer(handler)
}

Expand All @@ -90,8 +95,20 @@ func index(w http.ResponseWriter, r *http.Request) {
}
}

func userAccountList(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
if os.Getenv("TEST_FAIL_QUERY") != "" {
w.WriteHeader(http.StatusInternalServerError)
} else {
_, _ = w.Write(usersPayload)
}
case "PUT":
httpRequestTestVar = r
}
}

func biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%s -> %s\n", r.Method, r.RequestURI)
switch r.Method {
case "POST":
switch r.RequestURI {
Expand All @@ -112,7 +129,6 @@ func biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) {
}

func bmcFirmwareUpgrade(w http.ResponseWriter, r *http.Request) {
fmt.Printf("%s -> %s\n", r.Method, r.RequestURI)
switch r.Method {
case "GET":
switch r.RequestURI {
Expand Down
Loading