storj.io/uplink@v1.13.0/access.go (about)

     1  // Copyright (C) 2020 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package uplink
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"strings"
    10  	"time"
    11  	_ "unsafe" // for go:linkname
    12  
    13  	"github.com/zeebo/errs"
    14  
    15  	"storj.io/common/encryption"
    16  	"storj.io/common/grant"
    17  	"storj.io/common/macaroon"
    18  	"storj.io/common/paths"
    19  	"storj.io/common/rpc"
    20  	"storj.io/common/storj"
    21  	"storj.io/uplink/private/metaclient"
    22  )
    23  
    24  // An Access Grant contains everything to access a project and specific buckets.
    25  // It includes a potentially-restricted API Key, a potentially-restricted set
    26  // of encryption information, and information about the Satellite responsible
    27  // for the project's metadata.
    28  type Access struct {
    29  	satelliteURL storj.NodeURL
    30  	apiKey       *macaroon.APIKey
    31  	encAccess    *grant.EncryptionAccess
    32  }
    33  
    34  // getAPIKey are exposing the state do private methods.
    35  //
    36  // NB: this is used with linkname in internal/expose.
    37  // It needs to be updated when this is updated.
    38  //
    39  //lint:ignore U1000, used with linkname
    40  //nolint:unused
    41  //go:linkname access_getAPIKey
    42  func access_getAPIKey(access *Access) *macaroon.APIKey { return access.apiKey }
    43  
    44  // getEncAccess are exposing the state do private methods.
    45  //
    46  // NB: this is used with linkname in internal/expose.
    47  // It needs to be updated when this is updated.
    48  //
    49  //lint:ignore U1000, used with linkname
    50  //nolint:unused
    51  //go:linkname access_getEncAccess
    52  func access_getEncAccess(access *Access) *grant.EncryptionAccess { return access.encAccess }
    53  
    54  // SharePrefix defines a prefix that will be shared.
    55  type SharePrefix struct {
    56  	Bucket string
    57  	// Prefix is the prefix of the shared object keys.
    58  	//
    59  	// Note: that within a bucket, the hierarchical key derivation scheme is
    60  	// delineated by forward slashes (/), so encryption information will be
    61  	// included in the resulting access grant to decrypt any key that shares
    62  	// the same prefix up until the last slash.
    63  	Prefix string
    64  }
    65  
    66  // Permission defines what actions can be used to share.
    67  type Permission struct {
    68  	// AllowDownload gives permission to download the object's content. It
    69  	// allows getting object metadata, but it does not allow listing buckets.
    70  	AllowDownload bool
    71  	// AllowUpload gives permission to create buckets and upload new objects.
    72  	// It does not allow overwriting existing objects unless AllowDelete is
    73  	// granted too.
    74  	AllowUpload bool
    75  	// AllowList gives permission to list buckets. It allows getting object
    76  	// metadata, but it does not allow downloading the object's content.
    77  	AllowList bool
    78  	// AllowDelete gives permission to delete buckets and objects. Unless
    79  	// either AllowDownload or AllowList is granted too, no object metadata and
    80  	// no error info will be returned for deleted objects.
    81  	AllowDelete bool
    82  	// AllowLock gives permission for retention periods and legal holds to be
    83  	// placed on and retrieved from objects. It also gives permission for
    84  	// Object Lock configurations to be placed on and retrieved from buckets.
    85  	AllowLock bool
    86  	// NotBefore restricts when the resulting access grant is valid for.
    87  	// If set, the resulting access grant will not work if the Satellite
    88  	// believes the time is before NotBefore.
    89  	// If set, this value should always be before NotAfter.
    90  	NotBefore time.Time
    91  	// NotAfter restricts when the resulting access grant is valid for.
    92  	// If set, the resulting access grant will not work if the Satellite
    93  	// believes the time is after NotAfter.
    94  	// If set, this value should always be after NotBefore.
    95  	NotAfter time.Time
    96  	// MaxObjectTTL restricts the maximum time-to-live of objects.
    97  	// If set, new objects are uploaded with an expiration time that reflects
    98  	// the MaxObjectTTL period.
    99  	// If objects are uploaded with an explicit expiration time, the upload
   100  	// will be successful only if it is shorter than the MaxObjectTTL period.
   101  	MaxObjectTTL *time.Duration
   102  }
   103  
   104  // ParseAccess parses a serialized access grant string.
   105  //
   106  // This should be the main way to instantiate an access grant for opening a project.
   107  // See the note on RequestAccessWithPassphrase.
   108  func ParseAccess(access string) (*Access, error) {
   109  	inner, err := grant.ParseAccess(access)
   110  	if err != nil {
   111  		return nil, packageError.Wrap(err)
   112  	}
   113  
   114  	satelliteURL, err := parseNodeURL(inner.SatelliteAddress)
   115  	if err != nil {
   116  		return nil, packageError.Wrap(err)
   117  	}
   118  
   119  	return &Access{
   120  		satelliteURL: satelliteURL,
   121  		apiKey:       inner.APIKey,
   122  		encAccess:    inner.EncAccess,
   123  	}, nil
   124  }
   125  
   126  // SatelliteAddress returns the satellite node URL for this access grant.
   127  func (access *Access) SatelliteAddress() string {
   128  	return access.satelliteURL.String()
   129  }
   130  
   131  // Serialize serializes an access grant such that it can be used later with
   132  // ParseAccess or other tools.
   133  func (access *Access) Serialize() (string, error) {
   134  	inner := grant.Access{
   135  		SatelliteAddress: access.satelliteURL.String(),
   136  		APIKey:           access.apiKey,
   137  		EncAccess:        access.encAccess,
   138  	}
   139  	return inner.Serialize()
   140  }
   141  
   142  // RequestAccessWithPassphrase generates a new access grant using a passhprase.
   143  // It must talk to the Satellite provided to get a project-based salt for
   144  // deterministic key derivation.
   145  //
   146  // Note: this is a CPU-heavy function that uses a password-based key derivation function
   147  // (Argon2). This should be a setup-only step. Most common interactions with the library
   148  // should be using a serialized access grant through ParseAccess directly.
   149  func RequestAccessWithPassphrase(ctx context.Context, satelliteAddress, apiKey, passphrase string) (*Access, error) {
   150  	return (Config{}).RequestAccessWithPassphrase(ctx, satelliteAddress, apiKey, passphrase)
   151  }
   152  
   153  // RequestAccessWithPassphrase generates a new access grant using a passhprase.
   154  // It must talk to the Satellite provided to get a project-based salt for
   155  // deterministic key derivation.
   156  //
   157  // Note: this is a CPU-heavy function that uses a password-based key derivation function
   158  // (Argon2). This should be a setup-only step. Most common interactions with the library
   159  // should be using a serialized access grant through ParseAccess directly.
   160  func (config Config) RequestAccessWithPassphrase(ctx context.Context, satelliteAddress, apiKey, passphrase string) (*Access, error) {
   161  	return config_requestAccessWithPassphraseAndConcurrency(config, ctx, satelliteAddress, apiKey, passphrase, 8)
   162  }
   163  
   164  // requestAccessWithPassphraseAndConcurrency requests satellite for a new access grant using a passhprase and specific concurrency for the Argon2 key derivation.
   165  //
   166  // NB: this is used with linkname in internal/expose.
   167  // It needs to be updated when this is updated.
   168  //
   169  //nolint:revive
   170  //go:linkname config_requestAccessWithPassphraseAndConcurrency
   171  func config_requestAccessWithPassphraseAndConcurrency(config Config, ctx context.Context, satelliteAddress, apiKey, passphrase string, concurrency uint8) (_ *Access, err error) {
   172  	parsedAPIKey, err := macaroon.ParseAPIKey(apiKey)
   173  	if err != nil {
   174  		return nil, packageError.Wrap(err)
   175  	}
   176  
   177  	satelliteURL, err := parseNodeURL(satelliteAddress)
   178  	if err != nil {
   179  		return nil, packageError.Wrap(err)
   180  	}
   181  
   182  	dialer, err := config.getDialer(ctx)
   183  	if err != nil {
   184  		return nil, packageError.Wrap(err)
   185  	}
   186  	defer func() { err = errs.Combine(err, dialer.Pool.Close()) }()
   187  
   188  	metainfo, err := metaclient.DialNodeURL(ctx, dialer, satelliteURL.String(), parsedAPIKey, config.UserAgent)
   189  	if err != nil {
   190  		return nil, packageError.Wrap(err)
   191  	}
   192  	defer func() { err = errs.Combine(err, metainfo.Close()) }()
   193  
   194  	info, err := metainfo.GetProjectInfo(ctx)
   195  	if err != nil {
   196  		return nil, convertKnownErrors(err, "", "")
   197  	}
   198  
   199  	key, err := encryption.DeriveRootKey([]byte(passphrase), info.ProjectSalt, "", concurrency)
   200  	if err != nil {
   201  		return nil, packageError.Wrap(err)
   202  	}
   203  
   204  	encAccess := grant.NewEncryptionAccessWithDefaultKey(key)
   205  	encAccess.SetDefaultPathCipher(storj.EncAESGCM)
   206  	if config.disableObjectKeyEncryption {
   207  		encAccess.SetDefaultPathCipher(storj.EncNull)
   208  	}
   209  	encAccess.LimitTo(parsedAPIKey)
   210  
   211  	return &Access{
   212  		satelliteURL: satelliteURL,
   213  		apiKey:       parsedAPIKey,
   214  		encAccess:    encAccess,
   215  	}, nil
   216  }
   217  
   218  // parseNodeURL parses the address into a storj.NodeURL adding the node id if necessary
   219  // for known addresses.
   220  func parseNodeURL(address string) (storj.NodeURL, error) {
   221  	nodeURL, err := storj.ParseNodeURL(address)
   222  	if err != nil {
   223  		return nodeURL, packageError.Wrap(err)
   224  	}
   225  
   226  	// Node id is required in satelliteNodeID for all unknown (non-storj) satellites.
   227  	// For known satellite it will be automatically prepended.
   228  	if nodeURL.ID.IsZero() {
   229  		nodeID, found := rpc.KnownNodeID(nodeURL.Address)
   230  		if !found {
   231  			return nodeURL, packageError.New("node id is required in satelliteNodeURL")
   232  		}
   233  		nodeURL.ID = nodeID
   234  	}
   235  
   236  	return nodeURL, nil
   237  }
   238  
   239  // Share creates a new access grant with specific permissions.
   240  //
   241  // Access grants can only have their existing permissions restricted,
   242  // and the resulting access grant will only allow for the intersection of all previous
   243  // Share calls in the access grant construction chain.
   244  //
   245  // Prefixes, if provided, restrict the access grant (and internal encryption information)
   246  // to only contain enough information to allow access to just those prefixes.
   247  //
   248  // To revoke an access grant see the Project.RevokeAccess method.
   249  func (access *Access) Share(permission Permission, prefixes ...SharePrefix) (*Access, error) {
   250  	internalPrefixes := make([]grant.SharePrefix, 0, len(prefixes))
   251  	for _, prefix := range prefixes {
   252  		internalPrefixes = append(internalPrefixes, grant.SharePrefix(prefix))
   253  	}
   254  	rv, err := access.toInternal().Restrict(
   255  		grant.Permission{
   256  			AllowDownload: permission.AllowDownload,
   257  			AllowUpload:   permission.AllowUpload,
   258  			AllowList:     permission.AllowList,
   259  			AllowDelete:   permission.AllowDelete,
   260  			AllowLock:     permission.AllowLock,
   261  			NotBefore:     permission.NotBefore,
   262  			NotAfter:      permission.NotAfter,
   263  			MaxObjectTTL:  permission.MaxObjectTTL,
   264  		},
   265  		internalPrefixes...,
   266  	)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	return accessFromInternal(rv)
   271  }
   272  
   273  func (access *Access) toInternal() *grant.Access {
   274  	return &grant.Access{
   275  		SatelliteAddress: access.satelliteURL.String(),
   276  		APIKey:           access.apiKey,
   277  		EncAccess:        access.encAccess,
   278  	}
   279  }
   280  
   281  func accessFromInternal(access *grant.Access) (*Access, error) {
   282  	satelliteURL, err := parseNodeURL(access.SatelliteAddress)
   283  	if err != nil {
   284  		return nil, packageError.Wrap(err)
   285  	}
   286  
   287  	return &Access{
   288  		satelliteURL: satelliteURL,
   289  		apiKey:       access.APIKey,
   290  		encAccess:    access.EncAccess,
   291  	}, nil
   292  }
   293  
   294  // RevokeAccess revokes the API key embedded in the provided access grant.
   295  //
   296  // When an access grant is revoked, it will also revoke any further-restricted
   297  // access grants created (via the Access.Share method) from the revoked access
   298  // grant.
   299  //
   300  // An access grant is authorized to revoke any further-restricted access grant
   301  // created from it. An access grant cannot revoke itself. An unauthorized
   302  // request will return an error.
   303  //
   304  // There may be a delay between a successful revocation request and actual
   305  // revocation, depending on the satellite's access caching policies.
   306  func (project *Project) RevokeAccess(ctx context.Context, access *Access) (err error) {
   307  	defer mon.Task()(&ctx)(&err)
   308  
   309  	metainfoClient, err := project.dialMetainfoClient(ctx)
   310  	if err != nil {
   311  		return err
   312  	}
   313  	defer func() { err = errs.Combine(err, metainfoClient.Close()) }()
   314  
   315  	err = metainfoClient.RevokeAPIKey(ctx, metaclient.RevokeAPIKeyParams{
   316  		APIKey: access.apiKey.SerializeRaw(),
   317  	})
   318  	return convertKnownErrors(err, "", "")
   319  }
   320  
   321  // ReadOnlyPermission returns a Permission that allows reading and listing
   322  // (if the parent access grant already allows those things).
   323  func ReadOnlyPermission() Permission {
   324  	return Permission{
   325  		AllowDownload: true,
   326  		AllowList:     true,
   327  	}
   328  }
   329  
   330  // WriteOnlyPermission returns a Permission that allows writing and deleting
   331  // (if the parent access grant already allows those things).
   332  func WriteOnlyPermission() Permission {
   333  	return Permission{
   334  		AllowUpload: true,
   335  		AllowDelete: true,
   336  	}
   337  }
   338  
   339  // FullPermission returns a Permission that allows all actions that the
   340  // parent access grant already allows.
   341  func FullPermission() Permission {
   342  	return Permission{
   343  		AllowDownload: true,
   344  		AllowUpload:   true,
   345  		AllowList:     true,
   346  		AllowDelete:   true,
   347  		AllowLock:     true,
   348  	}
   349  }
   350  
   351  // OverrideEncryptionKey overrides the root encryption key for the prefix in
   352  // bucket with encryptionKey.
   353  // The prefix argument must end with slash, otherwise the method returns an
   354  // error.
   355  //
   356  // This function is useful for overriding the encryption key in user-specific
   357  // access grants when implementing multitenancy in a single app bucket.
   358  // See the relevant section in the package documentation.
   359  func (access *Access) OverrideEncryptionKey(bucket, prefix string, encryptionKey *EncryptionKey) error {
   360  	if !strings.HasSuffix(prefix, "/") {
   361  		return errors.New("prefix must end with slash")
   362  	}
   363  
   364  	// We need to remove the trailing slash. Otherwise, if we the shared
   365  	// prefix is `/bob/`, the encrypted shared prefix results in
   366  	// `enc("")/enc("bob")/enc("")`. This is an incorrect encrypted prefix,
   367  	// what we really want is `enc("")/enc("bob")`.
   368  	prefix = strings.TrimSuffix(prefix, "/")
   369  
   370  	store := access.encAccess.Store
   371  
   372  	unencPath := paths.NewUnencrypted(prefix)
   373  	encPath, err := encryption.EncryptPathWithStoreCipher(bucket, unencPath, store)
   374  	if err != nil {
   375  		return convertKnownErrors(err, bucket, prefix)
   376  	}
   377  
   378  	err = store.Add(bucket, unencPath, encPath, *encryptionKey.key)
   379  	return convertKnownErrors(err, bucket, prefix)
   380  }