storagenode/secret: db tests added, small renaming fixes added
Change-Id: I7eae1a9a64c20a39c97e81fa741cfc9b9e1e615a
This commit is contained in:
parent
caefde6b32
commit
624255e8ba
1
go.mod
1
go.mod
@ -31,6 +31,7 @@ require (
|
|||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/stripe/stripe-go v70.15.0+incompatible
|
github.com/stripe/stripe-go v70.15.0+incompatible
|
||||||
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3
|
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3
|
||||||
|
github.com/zeebo/assert v1.1.0
|
||||||
github.com/zeebo/errs v1.2.2
|
github.com/zeebo/errs v1.2.2
|
||||||
go.etcd.io/bbolt v1.3.5
|
go.etcd.io/bbolt v1.3.5
|
||||||
go.uber.org/zap v1.16.0
|
go.uber.org/zap v1.16.0
|
||||||
|
81
storagenode/apikey/apikeys.go
Normal file
81
storagenode/apikey/apikeys.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (C) 2020 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package apikey
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeebo/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNoSecret represents errors from the apikey database.
|
||||||
|
var ErrNoSecret = errs.Class("no apikey error")
|
||||||
|
|
||||||
|
// DB is interface for working with apikey tokens.
|
||||||
|
//
|
||||||
|
// architecture: Database
|
||||||
|
type DB interface {
|
||||||
|
// Store stores apikey token into db.
|
||||||
|
Store(ctx context.Context, secret APIKey) error
|
||||||
|
|
||||||
|
// Check checks if unique apikey exists in db by token.
|
||||||
|
Check(ctx context.Context, token Secret) error
|
||||||
|
|
||||||
|
// Revoke removes token from db.
|
||||||
|
Revoke(ctx context.Context, token Secret) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secret stores token of storagenode APIkey.
|
||||||
|
type Secret [32]byte
|
||||||
|
|
||||||
|
// APIKey describing apikey model in the database.
|
||||||
|
type APIKey struct {
|
||||||
|
// Secret is PK of the table and keeps unique value sno apikey token
|
||||||
|
Secret Secret
|
||||||
|
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSecretToken creates new apikey token.
|
||||||
|
func NewSecretToken() (Secret, error) {
|
||||||
|
var b [32]byte
|
||||||
|
|
||||||
|
_, err := rand.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
return b, errs.New("error creating apikey token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Stringer.
|
||||||
|
func (secret Secret) String() string {
|
||||||
|
return base64.URLEncoding.EncodeToString(secret[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns if the apikey token is not set.
|
||||||
|
func (secret Secret) IsZero() bool {
|
||||||
|
var zero Secret
|
||||||
|
// this doesn't need to be constant-time, because we're explicitly testing
|
||||||
|
// against a hardcoded, well-known value
|
||||||
|
return bytes.Equal(secret[:], zero[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSecretFromBase64 creates new apikey token from base64 string.
|
||||||
|
func TokenSecretFromBase64(s string) (Secret, error) {
|
||||||
|
var token Secret
|
||||||
|
|
||||||
|
b, err := base64.URLEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(token[:], b)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
51
storagenode/apikey/db_test.go
Normal file
51
storagenode/apikey/db_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2020 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package apikey_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zeebo/assert"
|
||||||
|
|
||||||
|
"storj.io/common/testcontext"
|
||||||
|
"storj.io/storj/storagenode"
|
||||||
|
"storj.io/storj/storagenode/apikey"
|
||||||
|
"storj.io/storj/storagenode/storagenodedb/storagenodedbtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSecretDB(t *testing.T) {
|
||||||
|
storagenodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db storagenode.DB) {
|
||||||
|
secrets := db.Secret()
|
||||||
|
token, err := apikey.NewSecretToken()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
token2, err := apikey.NewSecretToken()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("Test StoreSecret", func(t *testing.T) {
|
||||||
|
err := secrets.Store(ctx, apikey.APIKey{
|
||||||
|
Secret: token,
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test CheckSecret", func(t *testing.T) {
|
||||||
|
err := secrets.Check(ctx, token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = secrets.Check(ctx, token2)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test RevokeSecret", func(t *testing.T) {
|
||||||
|
err = secrets.Revoke(ctx, token)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = secrets.Check(ctx, token)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
@ -31,6 +31,7 @@ import (
|
|||||||
"storj.io/storj/private/version/checker"
|
"storj.io/storj/private/version/checker"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
"storj.io/storj/storage/filestore"
|
"storj.io/storj/storage/filestore"
|
||||||
|
"storj.io/storj/storagenode/apikey"
|
||||||
"storj.io/storj/storagenode/bandwidth"
|
"storj.io/storj/storagenode/bandwidth"
|
||||||
"storj.io/storj/storagenode/collector"
|
"storj.io/storj/storagenode/collector"
|
||||||
"storj.io/storj/storagenode/console"
|
"storj.io/storj/storagenode/console"
|
||||||
@ -54,7 +55,6 @@ import (
|
|||||||
"storj.io/storj/storagenode/reputation"
|
"storj.io/storj/storagenode/reputation"
|
||||||
"storj.io/storj/storagenode/retain"
|
"storj.io/storj/storagenode/retain"
|
||||||
"storj.io/storj/storagenode/satellites"
|
"storj.io/storj/storagenode/satellites"
|
||||||
"storj.io/storj/storagenode/secret"
|
|
||||||
"storj.io/storj/storagenode/storagenodedb"
|
"storj.io/storj/storagenode/storagenodedb"
|
||||||
"storj.io/storj/storagenode/storageusage"
|
"storj.io/storj/storagenode/storageusage"
|
||||||
"storj.io/storj/storagenode/trust"
|
"storj.io/storj/storagenode/trust"
|
||||||
@ -87,7 +87,7 @@ type DB interface {
|
|||||||
Notifications() notifications.DB
|
Notifications() notifications.DB
|
||||||
Payout() payout.DB
|
Payout() payout.DB
|
||||||
Pricing() pricing.DB
|
Pricing() pricing.DB
|
||||||
Secret() secret.DB
|
Secret() apikey.DB
|
||||||
|
|
||||||
Preflight(ctx context.Context) error
|
Preflight(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
// Copyright (C) 2020 Storj Labs, Inc.
|
|
||||||
// See LICENSE for copying information.
|
|
||||||
|
|
||||||
package secret
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zeebo/errs"
|
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNoSecret represents errors from the secret database.
|
|
||||||
var ErrNoSecret = errs.Class("no secret error")
|
|
||||||
|
|
||||||
// DB is interface for working with secret tokens.
|
|
||||||
//
|
|
||||||
// architecture: Database
|
|
||||||
type DB interface {
|
|
||||||
// Store stores secret token into db.
|
|
||||||
Store(ctx context.Context, secret UniqSecret) error
|
|
||||||
|
|
||||||
// Check checks if uniq secret exists in db by token.
|
|
||||||
Check(ctx context.Context, token uuid.UUID) (_ bool, err error)
|
|
||||||
|
|
||||||
// Revoke removes token from db.
|
|
||||||
Revoke(ctx context.Context, token uuid.UUID) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token stores secret of sno token.
|
|
||||||
type Token [32]byte
|
|
||||||
|
|
||||||
// UniqSecret describing secret model in the database.
|
|
||||||
type UniqSecret struct {
|
|
||||||
// Secret is PK of the table and keeps unique value sno secret token
|
|
||||||
Secret Token
|
|
||||||
|
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSecretToken creates new secret token.
|
|
||||||
func NewSecretToken() (Token, error) {
|
|
||||||
var b [32]byte
|
|
||||||
|
|
||||||
_, err := rand.Read(b[:])
|
|
||||||
if err != nil {
|
|
||||||
return b, errs.New("error creating secret token")
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String implements Stringer.
|
|
||||||
func (secret Token) String() string {
|
|
||||||
return base64.URLEncoding.EncodeToString(secret[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsZero returns if the secret token is not set.
|
|
||||||
func (secret Token) IsZero() bool {
|
|
||||||
var zero Token
|
|
||||||
// this doesn't need to be constant-time, because we're explicitly testing
|
|
||||||
// against a hardcoded, well-known value
|
|
||||||
return bytes.Equal(secret[:], zero[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// TokenSecretFromBase64 creates new secret token from base64 string.
|
|
||||||
func TokenSecretFromBase64(s string) (Token, error) {
|
|
||||||
var secret Token
|
|
||||||
|
|
||||||
b, err := base64.URLEncoding.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
return secret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(secret[:], b)
|
|
||||||
|
|
||||||
return secret, nil
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
// Copyright (C) 2020 Storj Labs, Inc.
|
|
||||||
// See LICENSE for copying information.
|
|
||||||
|
|
||||||
package secret
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zeebo/errs"
|
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrSecretService defines secret service error.
|
|
||||||
var ErrSecretService = errs.Class("secret service error")
|
|
||||||
|
|
||||||
// Service responsible for operations with storagenode's uniq secret.
|
|
||||||
//
|
|
||||||
// architecture: Service
|
|
||||||
type Service struct {
|
|
||||||
store DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue generates new storagenode uniq secret and stores it into db.
|
|
||||||
func (service *Service) Issue(ctx context.Context) error {
|
|
||||||
var secret UniqSecret
|
|
||||||
|
|
||||||
token, err := NewSecretToken()
|
|
||||||
if err != nil {
|
|
||||||
return ErrSecretService.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secret.Secret = token
|
|
||||||
secret.CreatedAt = time.Now().UTC()
|
|
||||||
|
|
||||||
err = service.store.Store(ctx, secret)
|
|
||||||
if err != nil {
|
|
||||||
return ErrSecretService.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check returns boolean values if unique secret exists in db by secret token.
|
|
||||||
func (service *Service) Check(ctx context.Context, token uuid.UUID) (_ bool, err error) {
|
|
||||||
isExists, err := service.store.Check(ctx, token)
|
|
||||||
if err != nil {
|
|
||||||
if ErrNoSecret.Has(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, ErrSecretService.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return isExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove revokes token, delete's it from db.
|
|
||||||
func (service *Service) Remove(ctx context.Context, token uuid.UUID) error {
|
|
||||||
err := service.store.Revoke(ctx, token)
|
|
||||||
if err != nil {
|
|
||||||
return ErrSecretService.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
82
storagenode/storagenodedb/apikeys.go
Normal file
82
storagenode/storagenodedb/apikeys.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (C) 2020 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package storagenodedb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/zeebo/errs"
|
||||||
|
|
||||||
|
"storj.io/storj/storagenode/apikey"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensures that secretDB implements apikey.DB interface.
|
||||||
|
var _ apikey.DB = (*secretDB)(nil)
|
||||||
|
|
||||||
|
// ErrSecret represents errors from the apikey database.
|
||||||
|
var ErrSecret = errs.Class("apikey db error")
|
||||||
|
|
||||||
|
// SecretDBName represents the database name.
|
||||||
|
const SecretDBName = "secret"
|
||||||
|
|
||||||
|
// secretDB works with node apikey DB.
|
||||||
|
type secretDB struct {
|
||||||
|
dbContainerImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store stores apikey into database.
|
||||||
|
func (db *secretDB) Store(ctx context.Context, secret apikey.APIKey) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
query := `INSERT INTO secret (
|
||||||
|
token,
|
||||||
|
created_at
|
||||||
|
) VALUES(?,?)`
|
||||||
|
|
||||||
|
_, err = db.ExecContext(ctx, query,
|
||||||
|
secret.Secret[:],
|
||||||
|
secret.CreatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ErrSecret.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check checks if apikey exists in db by token.
|
||||||
|
func (db *secretDB) Check(ctx context.Context, token apikey.Secret) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
var bytes []uint8
|
||||||
|
var createdAt string
|
||||||
|
|
||||||
|
rowStub := db.QueryRowContext(ctx,
|
||||||
|
`SELECT token, created_at FROM secret WHERE token = ?`,
|
||||||
|
token[:],
|
||||||
|
)
|
||||||
|
|
||||||
|
err = rowStub.Scan(
|
||||||
|
&bytes,
|
||||||
|
&createdAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return apikey.ErrNoSecret.Wrap(err)
|
||||||
|
}
|
||||||
|
return ErrSecret.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke removes apikey from db.
|
||||||
|
func (db *secretDB) Revoke(ctx context.Context, secret apikey.Secret) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
query := `DELETE FROM secret WHERE token = ?`
|
||||||
|
|
||||||
|
_, err = db.ExecContext(ctx, query, secret[:])
|
||||||
|
|
||||||
|
return ErrSecret.Wrap(err)
|
||||||
|
}
|
@ -25,6 +25,7 @@ import (
|
|||||||
"storj.io/storj/private/tagsql"
|
"storj.io/storj/private/tagsql"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
"storj.io/storj/storage/filestore"
|
"storj.io/storj/storage/filestore"
|
||||||
|
"storj.io/storj/storagenode/apikey"
|
||||||
"storj.io/storj/storagenode/bandwidth"
|
"storj.io/storj/storagenode/bandwidth"
|
||||||
"storj.io/storj/storagenode/notifications"
|
"storj.io/storj/storagenode/notifications"
|
||||||
"storj.io/storj/storagenode/orders"
|
"storj.io/storj/storagenode/orders"
|
||||||
@ -33,7 +34,6 @@ import (
|
|||||||
"storj.io/storj/storagenode/pricing"
|
"storj.io/storj/storagenode/pricing"
|
||||||
"storj.io/storj/storagenode/reputation"
|
"storj.io/storj/storagenode/reputation"
|
||||||
"storj.io/storj/storagenode/satellites"
|
"storj.io/storj/storagenode/satellites"
|
||||||
"storj.io/storj/storagenode/secret"
|
|
||||||
"storj.io/storj/storagenode/storageusage"
|
"storj.io/storj/storagenode/storageusage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -536,7 +536,7 @@ func (db *DB) Pricing() pricing.DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Secret returns instance of the Secret database.
|
// Secret returns instance of the Secret database.
|
||||||
func (db *DB) Secret() secret.DB {
|
func (db *DB) Secret() apikey.DB {
|
||||||
return db.secretDB
|
return db.secretDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
// Copyright (C) 2020 Storj Labs, Inc.
|
|
||||||
// See LICENSE for copying information.
|
|
||||||
|
|
||||||
package storagenodedb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/zeebo/errs"
|
|
||||||
|
|
||||||
"storj.io/common/uuid"
|
|
||||||
"storj.io/storj/storagenode/secret"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ensures that secretDB implements secret.Secret interface.
|
|
||||||
var _ secret.DB = (*secretDB)(nil)
|
|
||||||
|
|
||||||
// ErrSecret represents errors from the secret database.
|
|
||||||
var ErrSecret = errs.Class("secret db error")
|
|
||||||
|
|
||||||
// SecretDBName represents the database name.
|
|
||||||
const SecretDBName = "secret"
|
|
||||||
|
|
||||||
// secretDB works with node secret DB.
|
|
||||||
type secretDB struct {
|
|
||||||
dbContainerImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store stores now storagenode's uniq secret to database.
|
|
||||||
func (db *secretDB) Store(ctx context.Context, secret secret.UniqSecret) (err error) {
|
|
||||||
defer mon.Task()(&ctx)(&err)
|
|
||||||
|
|
||||||
query := `INSERT INTO secret (
|
|
||||||
token,
|
|
||||||
created_at
|
|
||||||
) VALUES(?,?)`
|
|
||||||
|
|
||||||
_, err = db.ExecContext(ctx, query,
|
|
||||||
secret.Secret,
|
|
||||||
secret.CreatedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
return ErrSecret.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check checks if uniq secret exists in db by token.
|
|
||||||
func (db *secretDB) Check(ctx context.Context, token uuid.UUID) (_ bool, err error) {
|
|
||||||
defer mon.Task()(&ctx)(&err)
|
|
||||||
|
|
||||||
result := secret.UniqSecret{}
|
|
||||||
|
|
||||||
rowStub := db.QueryRowContext(ctx,
|
|
||||||
`SELECT token, created_at FROM secret WHERE token = ?`,
|
|
||||||
token,
|
|
||||||
)
|
|
||||||
|
|
||||||
err = rowStub.Scan(
|
|
||||||
&result.Secret,
|
|
||||||
&result.CreatedAt,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return false, secret.ErrNoSecret.Wrap(err)
|
|
||||||
}
|
|
||||||
return false, ErrSecret.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoke removes token from db.
|
|
||||||
func (db *secretDB) Revoke(ctx context.Context, token uuid.UUID) (err error) {
|
|
||||||
defer mon.Task()(&ctx)(&err)
|
|
||||||
|
|
||||||
query := `DELETE FROM secret WHERE token = ?`
|
|
||||||
|
|
||||||
_, err = db.ExecContext(ctx, query, token)
|
|
||||||
|
|
||||||
return ErrSecret.Wrap(err)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user