diff --git a/docs/policy_converters.md b/docs/policy_converters.md new file mode 100644 index 0000000..5a2b1ce --- /dev/null +++ b/docs/policy_converters.md @@ -0,0 +1,439 @@ +# 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" + } + ] +} +```