Skip to content

Commit

Permalink
Merge pull request #27 from Luzilla/key-rotation
Browse files Browse the repository at this point in the history
key rotation
  • Loading branch information
till authored Oct 9, 2024
2 parents d42a321 + 397a10d commit 713d157
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 13 deletions.
11 changes: 11 additions & 0 deletions cmd/ostor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ func main() {
},
Action: cmd.RevokeKey,
},
{
Name: "rotate-key",
Flags: []cli.Flag{
emailFlag(),
&cli.StringFlag{
Name: "key-id",
Required: true,
},
},
Action: cmd.RotateKey,
},
{
Name: "limits",
Flags: []cli.Flag{
Expand Down
51 changes: 39 additions & 12 deletions internal/cmd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,29 +110,37 @@ func ShowUser(cCtx *cli.Context) error {
fmt.Printf("State: %s\n", user.State)
fmt.Println("")

tblAK := table.New("Key ID", "Secret Key ID")
tblAK.WithHeaderFormatter(headerFmt()).WithFirstColumnFormatter(columnFmt())
if len(user.AccessKeys) > 0 {
tblAK := table.New("Key ID", "Secret Key ID")
tblAK.WithHeaderFormatter(headerFmt()).WithFirstColumnFormatter(columnFmt())

for _, ak := range user.AccessKeys {
tblAK.AddRow(ak.AccessKeyID, ak.SecretAccessKey)
}
for _, ak := range user.AccessKeys {
tblAK.AddRow(ak.AccessKeyID, ak.SecretAccessKey)
}

tblAK.Print()
tblAK.Print()

fmt.Println("")
fmt.Println("")
} else {
errorNoticeFmt("User does not have any keys.")
}

buckets, _, err := client.GetBuckets(email)
if err != nil {
return err
}

tbl := table.New("Bucket", "Size (current)", "Created At")
tbl.WithHeaderFormatter(headerFmt()).WithFirstColumnFormatter(columnFmt())
if len(buckets.Buckets) > 0 {
tbl := table.New("Bucket", "Size (current)", "Created At")
tbl.WithHeaderFormatter(headerFmt()).WithFirstColumnFormatter(columnFmt())

for _, b := range buckets.Buckets {
tbl.AddRow(b.Name, utils.PrettyByteSize(b.Size.Current), b.CreatedAt)
for _, b := range buckets.Buckets {
tbl.AddRow(b.Name, utils.PrettyByteSize(b.Size.Current), b.CreatedAt)
}
tbl.Print()
} else {
errorNoticeFmt("User does not have any buckets.")
}
tbl.Print()

return nil
}
Expand Down Expand Up @@ -166,6 +174,25 @@ func CreateKey(cCtx *cli.Context) error {
return nil
}

func RotateKey(cCtx *cli.Context) error {
client := cCtx.Context.Value(OstorClient).(*ostor.Ostor)

email := cCtx.String("email")
keyID := cCtx.String("key-id")

keyPair, _, err := client.RotateKey(email, keyID)
if err != nil {
return err
}

fmt.Println("New key generated:")
fmt.Printf("Access Key ID: %s\n", keyPair.AccessKeyID)
fmt.Printf("Secret Access Key: %s\n", keyPair.SecretAccessKey)
fmt.Println("")

return nil
}

func UserLimits(cCtx *cli.Context) error {
client := cCtx.Context.Value(OstorClient).(*ostor.Ostor)

Expand Down
4 changes: 4 additions & 0 deletions internal/cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ func headerFmt() func(format string, a ...interface{}) string {
func columnFmt() func(format string, a ...interface{}) string {
return color.New(color.FgYellow).SprintfFunc()
}

func errorNoticeFmt(msg string) (int, error) {
return color.New(color.FgRed, color.BgHiWhite).Println(msg)
}
69 changes: 68 additions & 1 deletion pkg/ostor/key.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package ostor

import "github.com/go-resty/resty/v2"
import (
"fmt"
"net/http"

"github.com/go-resty/resty/v2"
)

func (o *Ostor) GenerateCredentials(email string) (*OstorCreateUserResponse, *resty.Response, error) {
var user *OstorCreateUserResponse
Expand All @@ -11,3 +16,65 @@ func (o *Ostor) GenerateCredentials(email string) (*OstorCreateUserResponse, *re
func (o *Ostor) RevokeKey(email, accessKeyID string) (*resty.Response, error) {
return o.post(qUsers, qUsers+"&emailAddress="+email+"&revokeKey="+accessKeyID, nil)
}

// RotateKey attempts to rotate the accessKeyID for the given user (email).
//
// This feature is not a native feature in the APIs, so we build around it using
// our own methods, by checking the user account and verifying what can happen.
func (o *Ostor) RotateKey(email, accessKeyID string) (*AccessKeyPair, *resty.Response, error) {
user, userResp, err := o.GetUser(email)
if err != nil {
if userResp.StatusCode() == http.StatusNotFound {
return nil, nil, fmt.Errorf("user %q does not exist", email)
}
return nil, userResp, err
}

if len(user.AccessKeys) == 0 {
return nil, nil, fmt.Errorf("user %q has no access keys to rotate", email)
}

foundKey := false

// each account can have up to 2 key pairs
var otherKeyPair *AccessKeyPair
for _, kP := range user.AccessKeys {
if kP.AccessKeyID == accessKeyID {
foundKey = true
continue
}

otherKeyPair = &kP
}

if !foundKey {
return nil, nil, fmt.Errorf("user %q has no access key %q that could be rotated", email, accessKeyID)
}

revokeResp, err := o.RevokeKey(email, accessKeyID)
if err != nil {
return nil, revokeResp, err
}

genUser, genResp, err := o.GenerateCredentials(email)
if err != nil {
return nil, genResp, fmt.Errorf("failed to generate new credentials for %q: %w", email, err)
}

if len(genUser.AccessKeys) == 0 {
return nil, nil, fmt.Errorf("no credentials were generated (ostor error) for %q", email)
}

if otherKeyPair == nil { // no other key was there, no need to filter
return &genUser.AccessKeys[0], genResp, nil
}

for _, kP := range genUser.AccessKeys {
if kP.AccessKeyID != otherKeyPair.AccessKeyID {
return &kP, genResp, nil
}
}

// this should not happen
return nil, genResp, fmt.Errorf("unable to find new key pair")
}

0 comments on commit 713d157

Please sign in to comment.