storj/cmd/uplinkng/cmd_access_inspect.go

174 lines
4.5 KiB
Go
Raw Normal View History

// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/btcsuite/btcutil/base58"
"github.com/zeebo/clingy"
"github.com/zeebo/errs"
"storj.io/common/macaroon"
"storj.io/common/pb"
"storj.io/storj/cmd/uplinkng/ulext"
)
// ensures that cmdAccessInspect implements clingy.Command.
var _ clingy.Command = (*cmdAccessInspect)(nil)
// cmdAccessInspect is an access inspect command itself.
type cmdAccessInspect struct {
ex ulext.External
accessNameOrValue string
}
// newCmdAccessInspect is a constructor for cmdAccessInspect.
func newCmdAccessInspect(ex ulext.External) clingy.Command {
return &cmdAccessInspect{ex: ex}
}
// Setup is called to define and parse arguments.
func (c *cmdAccessInspect) Setup(params clingy.Parameters) {
accessNameOrValue := params.Arg("access", "Inspect access by its name or value.", clingy.Optional).(*string)
if accessNameOrValue == nil {
c.accessNameOrValue = ""
return
}
c.accessNameOrValue = *accessNameOrValue
}
// Execute runs the command.
func (c *cmdAccessInspect) Execute(ctx clingy.Context) error {
accessExists, accessName := c.accessExists()
if !accessExists {
return errs.New("unknown access: %q", c.accessNameOrValue)
}
access, err := c.ex.OpenAccess(accessName)
if err != nil {
return errs.New("could not open access: %q, %+v", accessName, err)
}
serializedAccess, err := access.Serialize()
if err != nil {
return errs.New("could not serialize access: %+v", err)
}
p, err := parseAccessRaw(serializedAccess)
if err != nil {
return errs.New("could not parse access: %+v", err)
}
m, err := macaroon.ParseMacaroon(p.ApiKey)
if err != nil {
return errs.New("could not parse macaroon: %+v", err)
}
// TODO: this could be better
apiKey, err := macaroon.ParseRawAPIKey(p.ApiKey)
if err != nil {
return errs.New("could not parse api key: %+v", err)
}
accessInspect := accessInspect{
SatelliteAddr: p.SatelliteAddr,
EncryptionAccess: p.EncryptionAccess,
APIKey: apiKey.Serialize(),
Macaroon: accessInspectMacaroon{
Head: m.Head(),
Caveats: []macaroon.Caveat{},
Tail: m.Tail(),
},
}
for _, cb := range m.Caveats() {
var c macaroon.Caveat
err = pb.Unmarshal(cb, &c)
if err != nil {
return err
}
accessInspect.Macaroon.Caveats = append(accessInspect.Macaroon.Caveats, c)
}
bs, err := json.MarshalIndent(accessInspect, "", " ")
if err != nil {
return err
}
fmt.Fprintln(ctx, string(bs))
return nil
}
// accessExists checks if exists access with provided name or value.
func (c *cmdAccessInspect) accessExists() (bool, string) {
defaultAccessName, accesses, err := c.ex.GetAccessInfo(true)
if err != nil {
return false, ""
}
// show default access if parameter is absent.
if c.accessNameOrValue == "" {
c.accessNameOrValue = defaultAccessName
}
// trying to find access by name.
if _, ok := accesses[c.accessNameOrValue]; ok {
return true, c.accessNameOrValue
}
// trying to find access by value.
for accessName, accessValue := range accesses {
if accessValue == c.accessNameOrValue {
return true, accessName
}
}
return false, ""
}
// parseAccessRaw decodes Scope from base58 string, that contains SatelliteAddress, ApiKey, and EncryptionAccess.
func parseAccessRaw(access string) (_ *pb.Scope, err error) {
data, version, err := base58.CheckDecode(access)
if err != nil || version != 0 {
return nil, errs.New("invalid access grant format: %w", err)
}
p := new(pb.Scope)
if err := pb.Unmarshal(data, p); err != nil {
return nil, err
}
return p, nil
}
// accessInspect contains all info about access inspection that should be presented on cli.
type accessInspect struct {
SatelliteAddr string `json:"satellite_addr"`
EncryptionAccess *pb.EncryptionAccess `json:"encryption_access"`
APIKey string `json:"api_key"`
Macaroon accessInspectMacaroon `json:"macaroon"`
}
// base64url stores bytes representation of base64 encoded url.
type base64url []byte
// MarshalJSON implements the json.Marshaler interface for base64url.
func (b base64url) MarshalJSON() ([]byte, error) {
return []byte(`"` + base64.URLEncoding.EncodeToString(b) + `"`), nil
}
// accessInspectMacaroon contains all info about access macaroon that should be presented on cli.
type accessInspectMacaroon struct {
Head base64url `json:"head"`
Caveats []macaroon.Caveat `json:"caveats"`
Tail base64url `json:"tail"`
}