NeoFS has an awesome set of permissions you can give to containers.
ACL stands for access control list - its a list, or table really, of rules. They are checked top to bottom to any incoming request. The first permission entry in the table that the request passes, will end the checks. To that end it is important that all DENY
permissions come after any ALLOW
permissions.
When creating a container, you are going to need to create a permission. There are multiple preset permissions you can see in the table further down
permissions := acl.EACLReadOnlyBasicRule
print("please help by opening a Pull Request and filling in these code snippets!")
var permissions = (uint)BasicAcl.ReadOnlyBasicRule;
You can read more about ACL permissions within the NeoFS documentation however a set of available to use permissions are available. Most languages with an SDK will have constants set to these values
In the following table, names that start with EACL
(e.g EACLPrivateBasicRule
) allow for extended policies to be used in conjunction with the basic ACL permissions
Name | Value | Description |
---|---|---|
PublicBasicRule | 0x1FBFBFFF | PublicBasicRule is a basic ACL value for final public-read-write container for which extended ACL CANNOT be set. |
PrivateBasicRule | 0x1C8C8CCC | PrivateBasicRule is a basic ACL value for final private container for which extended ACL CANNOT be set. |
ReadOnlyBasicRule | 0x1FBF8CFF | ReadOnlyBasicRule is a basic ACL value for final public-read container for which extended ACL CANNOT be set. |
PublicAppendRule | 0x1FBF9FFF | PublicAppendRule is a basic ACL value for final public-append container for which extended ACL CANNOT be set. |
EACLPublicBasicRule | 0x0FBFBFFF | EACLPublicBasicRule is a basic ACL value for non-final public-read-write container for which extended ACL CAN be set. |
EACLPrivateBasicRule | 0x0C8C8CCC | EACLPrivateBasicRule is a basic ACL value for non-final private container for which extended ACL CAN be set. |
EACLReadOnlyBasicRule | 0x0FBF8CFF | EACLReadOnlyBasicRule is a basic ACL value for non-final public-read container for which extended ACL CAN be set. |
EACLPublicAppendRule | 0x0FBF9FFF | EACLPublicAppendRule is a basic ACL value for non-final public-append container for which extended ACL CAN be set. |
Nice to Know: the ability for a basic ACL permission to allow for Extended ACL permissions is controlled by the highest ‘bit’ in the above value. So comparing two of the above and comparing them in binary form
Name | Value | Binary |
---|---|---|
PublicBasicRule | 0x1FBFBFFF | 00011111101111111011111111111111 |
PrivateBasicRule | 0x1C8C8CCC | 00011100100011001000110011001100 |
EACLPublicAppendRule | 0x0FBF9FFF | 00001111101111111001111111111111 |
we can see here that the top two start 0001
however the final one starts 0000
. When this is a 1
, only BASIC ACL is processed.
Whats important is that if Basic ACL denies access, then Extended ACL cannot overrule.
Extended ACL permissions, are rules that can be applied over and beyond a container’s basic ACL permissions.
This is fantastically powerful as it allows a container owner to specify very strict and very specific rules to allow an account, or group of accounts to access certain objects within the container based on these extended rules.
Due to the capabilities of Extended ACL, there are a few concepts to understand
When thinking about these concepts, remember,
We are creating a rule for when an actor attempts to make a request to our container. These concepts are what make up that request
PUT, GET, HEAD, DELETE, SEARCH, RANGE, and RANGEHASH
against the containeruser
this is the container ownerothers
this refers to everyone elsesystem
this refers to nodes on the NeoFS network. It’s less likely you will use this targetALLOW
or DENY
the requestOnce you have this, the rule, containing these four parts, (which actually is a record) is added to an EACL table.
We are going to create an Extended ACL rule that
LetMeIn:OK
Note, there are multiple types of attribute filter you can apply, such as filtering by object length (AddObjectPayloadLengthFilter
) or filter by owner of the object (AddObjectOwnerIDFilter
). Check the eacl package for functions that add filters to records
Firstly we need two records. One for the others
and one for our privileged key
import "github.com/nspcc-dev/neofs-sdk-go/eacl"
othersRecord := eacl.NewRecord()
privRecord := eacl.NewRecord()
Quite simple, set both records to apply to GET
requests
othersRecord.SetOperation(eacl.OperationGet)
privRecord.SetOperation(eacl.OperationGet)
First we create a group that applies to the others
target group - the rule that will block anyone we don’t want accessing our container. Then add it to the record
othersTarget := eacl.NewTarget()
othersTarget.SetRole(eacl.RoleOthers) // set the role to 'others`
now create a target for a specific public key
// demo public key - 33-byte hex encoded public key from N3 wallet
const pubKeyStr = "03ab362a4eda62d22505ffe5a5e5422f1322317e8088afedb7c5029801e1ece806"
pub := keys.NewPublicKeyFromString(pubKeyStr)
privTarget := eacl.NewTarget()
eacl.SetTargetECDSAKeys(privTarget, pub)
Now we can add our targets to our record
othersRecord.SetTargets(othersTarget)
privRecord.SetTargets(privTarget)
Now we can use search capabilities against attributes on objects to filter even more specifically. In this case we are going to check that the object matches a specifc key, value pair
othersRecord.AddObjectAttributeFilter(eacl.MatchStringEqual, "LetMeIn", "OK")
privRecord.AddObjectAttributeFilter(eacl.MatchStringEqual, "LetMeIn", "OK")
Now both records state that for anyone, even the privileged key, to GET
a file, the attribute match.
Now we specify what each record should do if any part of the rule matches. The first match will result in that request being allowed or denied depending on what record matched
othersRecord.SetAction(eacl.ActionDeny)
privRecord.SetAction(eacl.ActionAllow)
To use these records now, they need to go into a table that references the container that they should be applied to
Remember to add the records ALLOW
followed by DENY
otherise all requests will match the first record (DENY
) and no account will be able to access the container
table := eacl.NewTable()
table.SetCID(someContainerID)
table.AddRecord(privRecord)
table.AddRecord(othersRecord)
DENY
rules in this case are redundant or that the default would result in the others
group being blocked automatically. On the other hand it may seem just to easy to miss a rule and accidentally let an acount through.You can read more about EACL rule ordering and execution here in the NeoFS spec however a high level overview:
The first thing you can do is specify this against the whole container. This maybe something you would do if you were wanting to share the container’s contents with someone.
You will need
_, err = cli.SetEACL(ctx, table)
if err != nil {
return fmt.Errorf("can't set extended ACL: %w", err)
}
at this point setting the EACL against the container can take some time. Waiting is necessary to get a response.
for i := 0; i <= 30; i++ {
if i == 30 {
log.Fatal("timeout, extended ACL was not persisted in side chain")
}
time.Sleep(time.Second)
_, err := cli.EACL(ctx, someContainerID)
if err != nil {
return fmt.Errorf("error setting eacl: %w", err)
}
}
Of course this can be handled better with channels
There is more on Bearer Tokens here, however you can add these Extended ACL rules to them so that the account that is issued with the token, can access objects within the container based on the Extended ACL rules.
To create a bearer token, you will need the private key of the container owner (containerOwnerKey
).
This is further explained in Bearer Tokens
btoken := new(token.BearerToken)
btoken.SetOwner(tokenReceiverID)
btoken.SetLifetime(100, 10, 1) // exp, nbf, iat arguments just like in JWT tokens. You can calculate the expiry using the current epoch and a number of epochs into the future
btoken.SetEACLTable(table) //table from earlier
err := btoken.SignToken(containerOwnerKey)
if err != nil {
return fmt.Errorf("error signing token: %w", err)
}
tokenBytes, err := btoken.MarshalJSON()
if err != nil {
return fmt.Errorf("error marshaling token: %w", err)
}
You can then issue the tokenBytes to whichever account should have it. See Bearer Tokens for more information