policy-engine/docs/policy_converters.md
Denis Kirillov 2e7518c453
All checks were successful
Tests and linters / Tests (1.20) (pull_request) Successful in 55s
Tests and linters / Tests (1.21) (pull_request) Successful in 1m5s
DCO action / DCO (pull_request) Successful in 58s
Tests and linters / Staticcheck (pull_request) Successful in 1m14s
Tests and linters / Tests with -race (pull_request) Successful in 1m31s
Tests and linters / Lint (pull_request) Successful in 2m16s
[#74] docs: Describe converters
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-05-07 13:10:02 +03:00

439 lines
16 KiB
Markdown

# Policy converters
This repository contains converters that provide opportunities to
transform [AWS IAM policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html) to inner
FrostFS policy format. This document describes such transformations.
## FrostFS
As it was mentioned there are converters that transform AWS IAM policies to FrostFS.
Here common examples of AWS:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "*"
}
]
}
```
and FrostFS:
```json
{
"ID": "c29tZS1pZA==",
"Rules": [
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"s3:*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"*"
]
},
"Any": false,
"Condition": null
}
],
"MatchType": "DenyPriority"
}
```
policies.
Despite there is only one FrostFS format, we have two converters (`s3` and `native`). The reason is S3 gateway and
Storage node have different actions and resource naming:
* S3 has [a lot of methods](https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations.html) and operates with
bucket/object
* Storage node has only 6 container and 7 object methods and operates container/object (that has different format)
The following sections describe each transformation more precisely ([common](#common) sections contains shared concepts)
### Common
#### Fields
Rough json main fields mapping:
| AWS policy field | FrostFS policy field | Comment |
|------------------|----------------------|------------------------------------------------------------------|
| `Version` | - | Not applicable |
| `Statement` | `Rules` | |
| `Effect` | `Status` | |
| `Action` | `Actions.Names` | `Actions.Inverted` = false |
| `NotAction` | `Actions.Names` | `Actions.Inverted` = true |
| `Resource` | `Resources.Names` | `Resources.Inverted` = false |
| `NotResource` | `Resources.Names` | `Resources.Inverted` = true |
| `Condition` | `Condition` | `Any` = false, that means the conditions must be hold altogether |
| `Principal` | - | Expressed via conditions (depends on s3/native converters) |
### Conditions
Each condition in FrostFS policy can add requirements to some request/resource properties
and consists of the following fields:
| Field | Description |
|----------|-------------------------------------------------------------------------------------------|
| `Op` | Condition type operation (`StringEqual`, `NumericEqual` etc) |
| `Object` | Property type to which condition can be applied (`Request` property, `Resource` property) |
| `Key` | Property key |
| `Value` | Property value |
Conditions operators:
| AWS conditions operator | FrostFS condition operator | Comment |
|-----------------------------|-----------------------------|-------------------------------------------------------------------|
| `StringEquals` | `StringEquals` | |
| `StringNotEquals` | `StringNotEquals` | |
| `StringEqualsIgnoreCase` | `StringEqualsIgnoreCase` | |
| `StringNotEqualsIgnoreCase` | `StringNotEqualsIgnoreCase` | |
| `StringLike` | `StringLike` | |
| `StringNotLike` | `StringNotLike` | |
| `NumericEquals` | `NumericEquals` | |
| `NumericNotEquals` | `NumericNotEquals` | |
| `NumericLessThan` | `NumericLessThan` | |
| `NumericLessThanEquals` | `NumericLessThanEquals` | |
| `NumericGreaterThan` | `NumericGreaterThan` | |
| `NumericGreaterThanEquals` | `NumericGreaterThanEquals` | |
| `DateEquals` | `StringEquals` | Date transforms to unix timestamp to be compared as string |
| `DateNotEquals` | `StringNotEquals` | Date transforms to unix timestamp to be compared as string |
| `DateLessThan` | `StringEqualsIgnoreCase` | Date transforms to unix timestamp to be compared as string |
| `DateLessThanEquals` | `StringNotEqualsIgnoreCase` | Date transforms to unix timestamp to be compared as string |
| `DateGreaterThan` | `StringLike` | Date transforms to unix timestamp to be compared as string |
| `DateGreaterThanEquals` | `StringNotLike` | Date transforms to unix timestamp to be compared as string |
| `Bool` | `StringEqualsIgnoreCase` | |
| `IpAddress` | `IPAddress` | |
| `NotIpAddress` | `NotIPAddress` | |
| `ArnEquals` | `StringEquals` | |
| `ArnLike` | `StringLike` | |
| `ArnNotEquals` | `StringNotEquals` | |
| `ArnNotLike` | `StringNotLike` | |
| `SliceContains` | `SliceContains` | AWS spec doesn't contain such operator. This is FrostFS extension |
For example, AWS conditions:
```json
{
"Condition": {
"ArnEquals": {"key16": ["val16"]},
"ArnNotEquals": {"key18": ["val18"]},
"ArnNotLike": {"key19": ["val19"]},
"Bool": {"key13": ["True"]},
"DateEquals": {"key7": ["2006-01-02T15:04:05+07:00"]},
"DateGreaterThan": {"key11": ["2006-01-02T15:04:05-01:00"]},
"DateGreaterThanEquals": {"key12": ["2006-01-02T15:04:05-03:00"]},
"DateLessThan": {"key9": ["2006-01-02T15:04:05+06:00"]},
"DateLessThanEquals": {"key10": ["2006-01-02T15:04:05+03:00"]},
"DateNotEquals": {"key8": ["2006-01-02T15:04:05Z"]},
"NumericEquals": {"key20": ["-20"]},
"NumericGreaterThan": {"key24": ["-24.24"]},
"NumericGreaterThanEquals": {"key25": ["+25.25"]},
"NumericLessThan": {"key22": ["0"]},
"NumericLessThanEquals": {"key23": ["23.23"]},
"NumericNotEquals": {"key21": ["+21"]},
"StringEquals": {"key1": ["val0"]},
"StringEqualsIgnoreCase": {"key3": ["val3"]},
"StringLike": {"key5": ["val5"]},
"StringNotEquals": {"key2": ["val2"]},
"StringNotEqualsIgnoreCase": {"key4": ["val4"]},
"StringNotLike": {"key6": ["val6"]}
}
}
```
transforms to FrostFS conditions:
```json
{
"Condition": [
{"Op": "StringLike", "Object": "Request", "Key": "key5", "Value": "val5"},
{"Op": "StringNotEquals", "Object": "Request", "Key": "key2", "Value": "val2"},
{"Op": "StringGreaterThan", "Object": "Request", "Key": "key11", "Value": "1136217845"},
{"Op": "StringGreaterThanEquals", "Object": "Request", "Key": "key12", "Value": "1136225045"},
{"Op": "StringLessThan", "Object": "Request", "Key": "key9", "Value": "1136192645"},
{"Op": "StringEqualsIgnoreCase", "Object": "Request", "Key": "key3", "Value": "val3"},
{"Op": "StringEquals", "Object": "Request", "Key": "key16", "Value": "val16"},
{"Op": "NumericLessThanEquals", "Object": "Request", "Key": "key23", "Value": "23.23"},
{"Op": "StringNotEqualsIgnoreCase", "Object": "Request", "Key": "key4", "Value": "val4"},
{"Op": "StringEquals", "Object": "Request", "Key": "key1", "Value": "val0"},
{"Op": "StringLessThanEquals", "Object": "Request", "Key": "key10", "Value": "1136203445"},
{"Op": "NumericGreaterThan", "Object": "Request", "Key": "key24", "Value": "-24.24"},
{"Op": "NumericGreaterThanEquals", "Object": "Request", "Key": "key25", "Value": "+25.25"},
{"Op": "NumericLessThan", "Object": "Request", "Key": "key22", "Value": "0"},
{"Op": "StringNotEquals", "Object": "Request", "Key": "key8", "Value": "1136214245"},
{"Op": "NumericEquals", "Object": "Request", "Key": "key20", "Value": "-20"},
{"Op": "NumericNotEquals", "Object": "Request", "Key": "key21", "Value": "+21"},
{"Op": "StringNotLike", "Object": "Request", "Key": "key6", "Value": "val6"},
{"Op": "StringNotEquals", "Object": "Request", "Key": "key18", "Value": "val18"},
{"Op": "StringNotLike", "Object": "Request", "Key": "key19", "Value": "val19"},
{"Op": "StringEqualsIgnoreCase", "Object": "Request", "Key": "key13", "Value": "True"},
{"Op": "StringEquals", "Object": "Request", "Key": "key7", "Value": "1136189045"}
]
}
```
### S3
#### Actions
Each action allows some s3-gw methods, so we must transform action to specific method names
(you can see exact mapping in table in [this file](../iam/converter_s3.go)).
For example the following actions:
```json
{
"Action": [
"s3:DeleteObject",
"iam:CreateUser"
]
}
```
transforms to
```json
{
"Actions": {
"Inverted": false,
"Names": [
"s3:DeleteObject",
"s3:DeleteMultipleObjects",
"iam:CreateUser"
]
}
}
```
As we can see any `iam:*` action transformed as it is. But `s3:*` actions transforms according to
[spec rules](https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazons3.html) and s3-gw
[method names](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/commit/2ab655b909c40db6f7a4e41e07d8b99167f791bd/api/middleware/constants.go#L3-L76).
#### Resources
Resource is transformed as it is:
```json
{
"Resource": [
"arn:aws:s3:::bucket/object"
]
}
```
```json
{
"Resources": {
"Inverted": false,
"Names": [
"arn:aws:s3:::bucket/object"
]
}
}
```
#### Principals
To check user s3-gw uses special condition request property (`Owner`), so when AWS policy contains principal field
it transforms to rule with appropriate condition. To get correct `Owner` property value special user resolver
(`S3Resolver` interface in [converter_s3 file](../iam/converter_s3.go)) must be provided into convert function.
For example such AWS json statement:
```json
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/JohnDoe"
}
}
```
transforms to the following FrostFS rule:
```json
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"*"
]
},
"Any": false,
"Condition": [
{
"Op": "StringEquals",
"Object": "Request",
"Key": "Owner",
"Value": "NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM"
}
]
}
```
### Native
#### Actions
Each action allows some frostfs methods, so we must transform action to specific method names
(you can see exact mapping in table in [this file](../iam/converter_native.go)).
For example the following actions:
```json
{
"Action": [
"s3:DeleteObject",
"iam:CreateUser"
]
}
```
transforms to
```json
{
"Actions": {
"Inverted": false,
"Names": [
"PutObject",
"HeadObject",
"GetObject",
"RangeObject",
"GetContainer",
"DeleteObject"
]
}
}
```
> **Note:** Only subset of s3:* actions can be transformed (exact value you can see in mapping table mentioned before).
> If all provided actions is not applicable converter function returns appropriate error.
Native methods (to which original actions are transformed) depend on which methods are invoked by appropriate s3-gw
[method](https://git.frostfs.info/TrueCloudLab/frostfs-s3-gw/src/commit/2ab655b909c40db6f7a4e41e07d8b99167f791bd/api/middleware/constants.go#L3-L76).
So in example above s3-gw during performing DeleteObject methods invokes the following methods:
`["PutObject","HeadObject","GetObject","RangeObject","GetContainer","DeleteObject"]`
#### Resources
To transform resources the following is being performed:
* Bucket name is resoled to container id (by providing `NativeResolver` interface implementation to converter)
* Object name is transformed to condition with special `FilePath` attribute
(that present on every object that was uploaded via s3-gw)
For example, the following AWS policy statement:
```json
{
"Principal": "*",
"Effect": "Allow",
"Action": "*",
"Resource": "arn:aws:s3:::bucket/object"
}
```
transforms to FrostFS native policy rule:
```json
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"native:object//bucket/HFq67qbfhFEiEL7qDXqayo3F78yAvxXSXzwSa2hKM9bH/*",
"native:container//bucket/HFq67qbfhFEiEL7qDXqayo3F78yAvxXSXzwSa2hKM9bH"
]
},
"Any": false,
"Condition": [
{
"Op": "StringLike",
"Object": "Resource",
"Key": "FilePath",
"Value": "object"
}
]
}
```
#### Principals
To check user s3-gw uses special condition request property (`$Actor:publicKey`), so when AWS policy contains principal
field it transforms to rule with appropriate condition. To get correct `$Actor:publicKey` property value
special user resolver (`NativeResolver` interface in [converter_native file](../iam/converter_native.go)) must be
provided into convert function.
For example such AWS json statement:
```json
{
"Effect": "Allow",
"Action": "*",
"Resource": "*",
"Principal": {
"AWS": "arn:aws:iam::111122223333:user/JohnDoe"
}
}
```
transforms to the following FrostFS rule:
```json
{
"Status": "Allow",
"Actions": {
"Inverted": false,
"Names": [
"*"
]
},
"Resources": {
"Inverted": false,
"Names": [
"native:object/*",
"native:container/*"
]
},
"Any": false,
"Condition": [
{
"Op": "StringEquals",
"Object": "Request",
"Key": "$Actor:publicKey",
"Value": "031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a"
}
]
}
```