storagenode/secret: db tests added, small renaming fixes added

Change-Id: I7eae1a9a64c20a39c97e81fa741cfc9b9e1e615a
This commit is contained in:
Qweder93 2020-10-19 14:11:25 +03:00
parent caefde6b32
commit 624255e8ba
9 changed files with 219 additions and 235 deletions

1
go.mod
View File

@ -31,6 +31,7 @@ require (
github.com/stretchr/testify v1.6.1
github.com/stripe/stripe-go v70.15.0+incompatible
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3
github.com/zeebo/assert v1.1.0
github.com/zeebo/errs v1.2.2
go.etcd.io/bbolt v1.3.5
go.uber.org/zap v1.16.0

View 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
}

View 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)
})
})
}

View File

@ -31,6 +31,7 @@ import (
"storj.io/storj/private/version/checker"
"storj.io/storj/storage"
"storj.io/storj/storage/filestore"
"storj.io/storj/storagenode/apikey"
"storj.io/storj/storagenode/bandwidth"
"storj.io/storj/storagenode/collector"
"storj.io/storj/storagenode/console"
@ -54,7 +55,6 @@ import (
"storj.io/storj/storagenode/reputation"
"storj.io/storj/storagenode/retain"
"storj.io/storj/storagenode/satellites"
"storj.io/storj/storagenode/secret"
"storj.io/storj/storagenode/storagenodedb"
"storj.io/storj/storagenode/storageusage"
"storj.io/storj/storagenode/trust"
@ -87,7 +87,7 @@ type DB interface {
Notifications() notifications.DB
Payout() payout.DB
Pricing() pricing.DB
Secret() secret.DB
Secret() apikey.DB
Preflight(ctx context.Context) error
}

View File

@ -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
}

View File

@ -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
}

View 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)
}

View File

@ -25,6 +25,7 @@ import (
"storj.io/storj/private/tagsql"
"storj.io/storj/storage"
"storj.io/storj/storage/filestore"
"storj.io/storj/storagenode/apikey"
"storj.io/storj/storagenode/bandwidth"
"storj.io/storj/storagenode/notifications"
"storj.io/storj/storagenode/orders"
@ -33,7 +34,6 @@ import (
"storj.io/storj/storagenode/pricing"
"storj.io/storj/storagenode/reputation"
"storj.io/storj/storagenode/satellites"
"storj.io/storj/storagenode/secret"
"storj.io/storj/storagenode/storageusage"
)
@ -536,7 +536,7 @@ func (db *DB) Pricing() pricing.DB {
}
// Secret returns instance of the Secret database.
func (db *DB) Secret() secret.DB {
func (db *DB) Secret() apikey.DB {
return db.secretDB
}

View File

@ -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)
}