174 lines
4.5 KiB
Go
174 lines
4.5 KiB
Go
|
// 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"`
|
||
|
}
|