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 }