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