diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 0000000..dfe859f --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,313 @@ +# Authentication and authorization scheme + +This document describes s3-gw authentication and authorization mechanism. + +## General overview + +Basic provisions: + +* A request to s3-gw can be signed or not (request that isn't signed we will cal anonymous or just anon) +* To manage resources (buckets/objects) using s3-gw you must have appropriate access rights + +Each request must be authenticated (at least as anonymous) and authorized. The following scheme shows components that +are involved to this +process. + + + Auth general overview + + +There are several participants of this process: + +1. User that make a request +2. S3-GW that accepts a request +3. FrostFS Storage that stores AccessObjects (objects are needed for authentication) +4. Blockchain smart contracts (`frostfsid`, `policy`) that stores user info and access rules. + +## Data auth process + +Let's look at the process in more detail: + + + Auth sequence diagram + + +* First of all, someone make a request. If request is signed we will check its signature (`Authentication`) after that + we will check access rights using policies (`Auhorization`). For anonymous requests only authorization be performed. + +* **Authentication steps**: + * Each signed request is provided with `AccessKeyId` and signature. So if request is signed we must check its + signature. To do this we must know the `AccessKeyId`/`SecretAccessKey` pair (How the signature is calculated + using this pair see [signing](#aws-signing). Client and server (s3-gw) use the same credentials and algorithm to + compute signature). The `AccessKeyId` is a public part of credentials, and it's passed to gate in request. The + private part of credentials is `SecretAccessKey` and it's encrypted and stored in [AccessBox](#accessbox). So on + this step we must find appropriate `AccessBox` in FrostFS storage node (How to find appropriate `AccessBox` + knowing `AccessKeyId` see [search algorithm](#search-algorithm)). On this stage we can get `AccessDenied` from + FrostFS storage node if the s3-gw doesn't have permission to read this `AccessBox` object. + + * After successful retrieving object we must extract `SecretAccessKey` from it. Since it's encrypted the s3-gw must + decrypt (see [encryption](#encryption)) this object using own private key and `SeedKey` from `AccessBox` + (see [AccessBox inner structure](#accessbox)). After s3-gw have got the `AccessKeyId`/`SecretAccessKey` pair it + [calculate signature](#aws-signing) and compare got signature with provided withing request. If signature doesn't + match the `AccessDenied` is returned. + + * `AccessBox` also contains `OwnerID` that is related to `AccessKeyId` that was provided. So we have to check if + such `OwnerID` exists in `frsotfsid` contract (that stores all registered valid users). If user doesn't exist in + contract the `AccessDenied` is returned. + +* **Authorization steps**: + * To know if user has access right to do what he wants to do we must find appropriate access policies. Such policies + are stored in `policy` contract and locally (can be manged using [control api](#control-auth-process)). So we need + to get policies from contract and [check them](#policies) along with local to decide if user has access right. If + he doesn't have such right the `AccessDenied` is returned. + +* After successful authentication and authorization the request will be processed by s3-gw business logic and finally be + propagated to FrostFS storage node which also performs some auth checks and can return `AccessDenied`. If this happens + s3-gw also returns `AccessDenied` as response. + +### AWS Signing + +Every interaction with FrostFS S3 gateway is either authenticated or anonymous. This section explains request +authentication with the AWS Signature Version 4 algorithm. More info in AWS documentation: + +* [Authenticating Requests (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) +* [Signing AWS API requests](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing.html) + +#### Authentication Methods + +You can express authentication information by using one of the following methods: + +* **HTTP Authorization header** - Using the HTTP Authorization header is the most common method of authenticating an + FrostFS S3 request. All the FrostFS S3 REST operations (except for browser-based uploads using POST requests) require + this header. For more information about the Authorization header value, and how to calculate signature and related + options, + see [Authenticating Requests: Using the Authorization Header (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html). +* **Query string parameters** - You can use a query string to express a request entirely in a URL. In this case, you use + query parameters to provide request information, including the authentication information. Because the request + signature is part of the URL, this type of URL is often referred to as a presigned URL. You can use presigned URLs to + embed clickable links, which can be valid for up to seven days, in HTML. For more information, + see [Authenticating Requests: Using Query Parameters (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html). + +FrostFS S3 also supports browser-based uploads that use HTTP POST requests. With an HTTP POST request, you can upload +content to FrostFS S3 directly from the browser. For information about authenticating POST requests, +see [Browser-Based Uploads Using POST (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html). + +#### Introduction to Signing Requests + +Authentication information that you send in a request must include a signature. To calculate a signature, you first +concatenate select request elements to form a string, referred to as the string to sign. You then use a signing key to +calculate the hash-based message authentication code (HMAC) of the string to sign. + +In AWS Signature Version 4, you don't use your secret access key to sign the request. Instead, you first use your secret +access key to derive a signing key. The derived signing key is specific to the date, service, and Region. For more +information about how to derive a signing key in different programming languages, see Examples of how to derive a +signing key for Signature Version 4. + +The following diagram illustrates the general process of computing a signature. + + + AWS Signing + + +The string to sign depends on the request type. For example, when you use the HTTP Authorization header or the query +parameters for authentication, you use a varying combination of request elements to create the string to sign. For an +HTTP POST request, the POST policy in the request is the string you sign. For more information about computing string to +sign, follow links provided at the end of this section. + +For signing key, the diagram shows series of calculations, where result of each step you feed into the next step. The +final step is the signing key. + +Upon receiving an authenticated request, FrostFS S3 servers re-create the signature by using the authentication +information that is contained in the request. If the signatures match, FrostFS S3 processes your request; otherwise, the +request is rejected. + +##### Signature Calculations for the Authorization Header + +To calculate a signature, you first need a string to sign. You then calculate a HMAC-SHA256 hash of the string to sign +by using a signing key. The following diagram illustrates the process, including the various components of the string +that you create for signing. + +When FrostFS S3 receives an authenticated request, it computes the signature and then compares it with the signature +that you provided in the request. For that reason, you must compute the signature by using the same method that is used +by FrostFS S3. The process of putting a request in an agreed-upon form for signing is called canonicalization. + + + Signature Calculations for the Authorization Header + + +See detains in [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html). + +#### s3-gw + +s3-gw support the following ways to provide the singed request: + +* [HTTP Authorization header](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html) +* [Query string parameters](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html) +* [Browser-Based Uploads Using POST](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html) + +All these methods provide `AccessKeyId` and signature. Using `AccessKeyId` s3-gw can get `SecretAccessKey` +(see [data auth](#data-auth-process)) to compute signature using exactly the same mechanics +as [client does](#introduction-to-signing-requests). After signature calculation the s3-gw just compares signatures and +if they don't match the access denied is returned. + +### AccessBox + +`AccessBox` is an ordinary object in FrostFS storage. It contains all information that can be used by s3-gw to +successfully authenticate request. Also, it contains data that is required to successful authentication in FrostFS +storage node. + +Based on this object s3 credentials are formed: + +* `AccessKeyId` - is concatenated container id and object id (`0`) of `AccessBox` ( + e.g. `2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf`) +* `SecretAccessKey` - hex-encoded random generated 32 bytes (that is encrypted and stored in object payload) + +> **Note**: sensitive info in `AccessBox` is [encrypted](#encryption), so only someone who posses specific private key +> can decrypt such info. + +`AccessBox` has the following structure: + + + AccessBox object structure + + +**Headers:** + +`AccessBox` object has the following attributes (at least them, it also can contain custom one): + +* `Timestamp` - unix timestamp when object was created +* `__SYSTEM__EXPIRATION_EPOCH` - epoch after which the object isn't available anymore +* `S3-CRDT-Versions-Add` - comma separated list of previous versions of `AccessBox` ( + see [AccessBox versions](#accessbox-versions)) +* `S3-Access-Box-CRDT-Name` - `AccessKeyId` of credentials to which current `AccessBox` is related ( + see [AccessBox versions](#accessbox-versions)) +* `FilePath` - just object name + +**Payload:** + +The `AccessBox` payload is an encoded [AccessBox protobuf type](../creds/accessbox/accessbox.proto) . +It contains: + +* Seed key - hex-encoded public seed key to compute shared secret using ECDH (see [encryption](#encryption)) +* List of gate data: + * Gate public key (so that gate (when it will decrypt data later) know which one item from list it should process) + * Encrypted tokens: + * `SecretAccessKey` - hex-encoded random generated 32 bytes + * Marshaled bearer token - more detail + in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/acl/types.proto#L189) + * Marshaled session token - more detail + in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/session/types.proto#L89) +* Container placement policies: + * `LocationsConstraint` - name of location constraint that can be used to create bucket/container using s3 + credentials related to this `AccessBox` + * Marshaled placement policy - more detail + in [spec](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/netmap/types.proto#L111) + +#### AccessBox versions + +Imagine the following scenario: + +* There is a system where only one s3-gw exist +* There is a `AccessBox` that can be used by this s3-gw +* User has s3 credentials (`AccessKeyId`/`SecretAccessKey`) related to corresponded `AccessBox` and can successfully + make request to s3-gw +* The system is expanded and new one s3-gw is added +* User must be able to use the credentials (that he has already had) to make request to new one s3-gw + +Since `AccessBox` object is immutable and `SecretAccessKey` is encrypted only for restricted list of keys (can be used +(decrypted) only by limited number of s3-gw) we have to create new `AccessBox` that has encrypted secrets for new list +of s3-gw and be related to initial s3 credentials (`AccessKeyId`/`SecretAccessKey`). Such relationship is done +by `S3-Access-Box-CRDT-Name`. + +##### Search algorithm + +To support scenario from previous section and find appropriate version of `AccessBox` (that contains more recent and +relevant data) the following sequence is used: + + + AccessBox search process + + +* Search all object whose attribute `S3-Access-Box-CRDT-Name` is equal to `AccessKeyId` (extract container id + from `AccessKeyId` that has format: `0`). +* Get metadata for these object using `HEAD` requests (not `Get` to reduce network traffic) +* Sort all these objects by creation epoch and object id +* Pick last object id (If no object is found then extract object id from `AccessKeyId` that has format: `0`. + We need to do this because versions of `AccessBox` can miss the `S3-Access-Box-CRDT-Name` attribute.) +* Get appropriate object from FrostFS storage +* Decrypt `AccessBox` (see [encryption](#encryption)) + +#### Encryption + +Each `AccessBox` contains sensitive information (`AccessSecretKey`, bearer/session tokens etc.) that must be protected +and available only to trusted parties (in our case it's a s3-gw). + +To encrypt/decrypt data the authenticated encryption with associated +data ([AEAD](https://en.wikipedia.org/wiki/Authenticated_encryption)) is used. The encryption algorithm +is [ChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) ([RFC](https://datatracker.ietf.org/doc/html/rfc7905)). + +Is the following algorithm the ECDSA keys (with curve implements NIST P-256 (FIPS 186-3, section D.2.3) also known as +secp256r1 or prime256v1) is used (unless otherwise stated). + +**Encryption:** + +* Create ephemeral key (`SeedKey`), it's need to generate shared secret +* Generate random 32-byte (that after hex-encoded be `SecretAccessKey`) or use existing secret access key + (if `AccessBox` is being updated rather than creating brand new) +* Generate shared secret as [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman) +* Derive 32-byte key using shared secret from previous step with key derivation function based on + HMAC with SHA256 [HKDF](https://en.wikipedia.org/wiki/HKDF) +* Encrypt marshaled [Tokens](../creds/accessbox) using derived key + with [ChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) algorithm without additional data. + +**Decryption:** + +* Get public part of `SeedKey` from `AccessBox` +* Generate shared secret as follows: + * Make scalar curve multiplication of public part of `SeedKey` and private part of s3-gw key + * Use `X` part of multiplication (with zero padding at the beginning to fit 32-byte) +* Derive 32-byte key using shared secret from previous step with key derivation function based on + HMAC with SHA256 [HKDF](https://en.wikipedia.org/wiki/HKDF) +* Decrypt encrypted marshaled [Tokens](../creds/accessbox) using derived key + with [ChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) algorithm without additional data. + +### Policies + +The main repository that contains policy implementation is https://git.frostfs.info/TrueCloudLab/policy-engine. + +Policies can be stored locally (using [control api](#control-auth-process)) or in `policy` contract. When policies check +is performed the following algorithm is applied: + +* Check local policies: + * If any rule was matched return checking result. +* Check contract policies: + * If any rule was matched return checking result. + * If no rules were matched return `deny` status. + +To local and contract policies `deny first` scheme is applied. This means that if several rules were matched for +reqeust (with both statuses `allow` and `deny`) the resulting status be `deny`. + +Policy rules validate if specified request can be performed on the specific resource. Request and resource can contain +some properties and rules can contain conditions on some such properties. + +In s3-gw resource is `/bucket/object`, `/bucket` or just `/` (if request is trying to list buckets). +Currently, request that is checked contains the following properties (so policy rule can contain conditions on them): + +* `Owner` - address of owner that is performing request (this is taken from bearer token from `AccessBox`) +* `frostfsid:groupID` - groups to which the owner belongs (this is taken from `frostfsid` contract) + +## Control auth process + +There are control path [grpc api](../pkg/service/control/service.proto) in s3-gw that also has their own authentication +and authorization process. + +But this process is quite straight forward: + +* Get grpc request +* Check if signing key belongs to [allowed key list](configuration.md#control-section) (that is located in config file) +* Validate signature + +For signing process the asymmetric encryption based on elliptic curves (`ECDSA_SHA512`) is used. +For more details see the appropriate code +in [frostfs-api](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/commit/4c68d92468503b10282c8a92af83a56f170c8a3a/refs/types.proto#L94) +and [frostfs-api-go](https://git.frostfs.info/TrueCloudLab/frostfs-api-go/src/commit/a85146250b312fcdd6da9a71285527fed544234f/refs/types.go#L38). diff --git a/docs/images/authentication/accessbox-object.puml b/docs/images/authentication/accessbox-object.puml new file mode 100644 index 0000000..d4bdb5d --- /dev/null +++ b/docs/images/authentication/accessbox-object.puml @@ -0,0 +1,44 @@ +@startuml + +package AccessBox { + map Tokens { + SecretKey => Private key + BearerToken => Encoded bearer token + SessionTokens => List of encoded session tokens + } + + map Gate { + GateKey => Encoded public gate key + Encrypted tokens *--> Tokens + } + + map ContainerPolicy { + LocationConstraint => Policy name + PlacementPolicy => Encoded placement policy + } + + map Box { + SeedKey => Encoded public seed key + List of Gates *--> Gate + List of container policies *--> ContainerPolicy + } + + + map ObjectAttributes { + Timestamp => 1710418478 + _~_SYSTEM_~_EXPIRATION_EPOCH => 10801 + S3-CRDT-Versions-Add => 5ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf,9bLtL1EsUpuSiqmHnqFf6RuT6x5QMLMNBqx7vCcCcNhy + S3-Access-Box-CRDT-Name => 2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf + FilePath => 1710418478_access.box + } + + map FrostFSObject { + Header *-> ObjectAttributes + Payload *--> Box + } + + +} + + +@enduml \ No newline at end of file diff --git a/docs/images/authentication/accessbox-object.svg b/docs/images/authentication/accessbox-object.svg new file mode 100644 index 0000000..fe2f2ef --- /dev/null +++ b/docs/images/authentication/accessbox-object.svg @@ -0,0 +1,60 @@ +AccessBoxTokensSecretKeyPrivate keyBearerTokenEncoded bearer tokenSessionTokensList of encoded session tokensGateGateKeyEncoded public gate keyEncrypted tokensContainerPolicyLocationConstraintPolicy namePlacementPolicyEncoded placement policyBoxSeedKeyEncoded public seed keyList of GatesList of container policiesObjectAttributesTimestamp1710418478__SYSTEM__EXPIRATION_EPOCH10801S3-CRDT-Versions-Add5ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3Snxf,9bLtL1EsUpuSiqmHnqFf6RuT6x5QMLMNBqx7vCcCcNhyS3-Access-Box-CRDT-Name2XGRML5EW3LMHdf64W2DkBy1Nkuu4y4wGhUj44QjbXBi05ZNvs8WVwy1XTmSEkcVkydPKzCgtmR7U3zyLYTj3SnxfFilePath1710418478_access.boxFrostFSObjectHeaderPayload \ No newline at end of file diff --git a/docs/images/authentication/accessbox-search.puml b/docs/images/authentication/accessbox-search.puml new file mode 100644 index 0000000..44c7cc7 --- /dev/null +++ b/docs/images/authentication/accessbox-search.puml @@ -0,0 +1,29 @@ +@startuml + +User -> "S3-GW": AccessKey +"S3-GW" -> "FrostFS Node": Search objects + +note right +Search by exact attribute matching: +**S3-Access-Box-CRDT-Name:** //AccessKey// +end note + +"FrostFS Node" --> "S3-GW": AccessBox objects ids + +"S3-GW" -> "FrostFS Node" : Head AccessBox objects +"FrostFS Node" --> "S3-GW": AccessBox object headers + +"S3-GW" -> "S3-GW": Choose latest AccessBox + +note left +Sort AccessBox headers by creation epoch +and then by ObjectID +Pick last +end note + +"S3-GW" -> "FrostFS Node" : Get AccessBox object +"FrostFS Node" --> "S3-GW": AccessBox object + +"S3-GW" -> "S3-GW": Decrypt and validate AccessBox + +@enduml \ No newline at end of file diff --git a/docs/images/authentication/accessbox-search.svg b/docs/images/authentication/accessbox-search.svg new file mode 100644 index 0000000..2152435 --- /dev/null +++ b/docs/images/authentication/accessbox-search.svg @@ -0,0 +1,39 @@ +UserUserS3-GWS3-GWFrostFS NodeFrostFS NodeAccessKeySearch objectsSearch by exact attribute matching:S3-Access-Box-CRDT-Name: AccessKeyAccessBox objects idsHead AccessBox objectsAccessBox object headersChoose latest AccessBoxSort AccessBox headers by creation epochand then by ObjectIDPick lastGet AccessBox objectAccessBox objectDecrypt and validate AccessBox \ No newline at end of file diff --git a/docs/images/authentication/auth-header-signing.png b/docs/images/authentication/auth-header-signing.png new file mode 100644 index 0000000..1a6e59f Binary files /dev/null and b/docs/images/authentication/auth-header-signing.png differ diff --git a/docs/images/authentication/auth-overview.puml b/docs/images/authentication/auth-overview.puml new file mode 100644 index 0000000..41e5898 --- /dev/null +++ b/docs/images/authentication/auth-overview.puml @@ -0,0 +1,25 @@ +@startuml +!include +AddElementTag("smart-contract", $bgColor=#0abab5) + +Person(user, "User", "User with or without credentials") + +System_Boundary(c1, "FrostFS") { + Container(s3, "S3 Gateway", $descr="AWS S3 compatible gate") + Container(stor, "FrostFS Storage", $descr="Storage service") +} + +System_Boundary(c3, "Blockchain") { + Interface "NeoGo" + Container(ffsid, "FrostFS ID", $tags="smart-contract", $descr="Stores namespaces and users") + Container(policy, "Policy", $tags="smart-contract", $descr="Stores APE rules") +} + +Rel_R(user, s3, "Requests", "HTTP") +Rel_R(s3, stor, "Get data to validate request, store objects") +Rel_D(s3, NeoGo, "Get data to validate request") +Rel("NeoGo", ffsid, "Fetch users") +Rel("NeoGo", policy, "Fetch policies") + +SHOW_LEGEND(true) +@enduml \ No newline at end of file diff --git a/docs/images/authentication/auth-overview.svg b/docs/images/authentication/auth-overview.svg new file mode 100644 index 0000000..6a429f7 --- /dev/null +++ b/docs/images/authentication/auth-overview.svg @@ -0,0 +1,611 @@ +FrostFS[System]Blockchain[System]S3 Gateway[] AWS S3 compatible gateFrostFS Storage[] Storage serviceNeoGoFrostFS ID[] Stores namespaces andusersPolicy[] Stores APE rulesUser User with or withoutcredentialsRequests[HTTP]Get data to validaterequest, store objectsGet data to validaterequestFetch usersFetch policiesLegend  person  container  smart-contract (no text color)  \ No newline at end of file diff --git a/docs/images/authentication/auth-sequence.puml b/docs/images/authentication/auth-sequence.puml new file mode 100644 index 0000000..e041474 --- /dev/null +++ b/docs/images/authentication/auth-sequence.puml @@ -0,0 +1,60 @@ +@startuml + +participant User +participant "S3-GW" +collections "FrostFS Storage" + +User -> "S3-GW": Request + +group signed request + + "S3-GW" -> "FrostFS Storage": Find Access Box + "FrostFS Storage" -> "FrostFS Storage": Check request + + alt #pink Check failure + "FrostFS Storage" -->> "S3-GW": Access Denied + "S3-GW" -->> User: Access Denied + end + + "FrostFS Storage" -->> "S3-GW": Access Box + "S3-GW" -> "S3-GW": Check sign + + alt #pink Check failure + "S3-GW" -->> User: Access Denied + end + + "S3-GW" -> "frostfsid contract": Find user + "frostfsid contract" -->> "S3-GW": User info + "S3-GW" -> "S3-GW": Check user info + + alt #pink Check failure + "S3-GW" -->> User: Access Denied + end + +end + +"S3-GW" -> "policy contract": Get policies +"policy contract" -->> "S3-GW": Policies +"S3-GW" -> "S3-GW": Check policy + +alt #pink Check failure + "S3-GW" -->> User: Access Denied +end + +"S3-GW" -> "FrostFS Storage": User Request +"FrostFS Storage" -> "FrostFS Storage": Check request + +alt #pink Check failure + "FrostFS Storage" -->> "S3-GW": Access Denied + "S3-GW" -->> User: Access Denied +end + +"FrostFS Storage" -->> "S3-GW": Response +"S3-GW" -->> User: Response + +box "Neo Go" + participant "frostfsid contract" + participant "policy contract" +end box + +@enduml \ No newline at end of file diff --git a/docs/images/authentication/auth-sequence.svg b/docs/images/authentication/auth-sequence.svg new file mode 100644 index 0000000..10c2f15 --- /dev/null +++ b/docs/images/authentication/auth-sequence.svg @@ -0,0 +1,70 @@ +Neo GoUserUserS3-GWS3-GWFrostFS StorageFrostFS Storagefrostfsid contractfrostfsid contractpolicy contractpolicy contractRequestsigned requestFind Access BoxCheck requestalt[Check failure]Access DeniedAccess DeniedAccess BoxCheck signalt[Check failure]Access DeniedFind userUser infoCheck user infoalt[Check failure]Access DeniedGet policiesPoliciesCheck policyalt[Check failure]Access DeniedUser RequestCheck requestalt[Check failure]Access DeniedAccess DeniedResponseResponse \ No newline at end of file diff --git a/docs/images/authentication/aws-signing.png b/docs/images/authentication/aws-signing.png new file mode 100644 index 0000000..d7caa7f Binary files /dev/null and b/docs/images/authentication/aws-signing.png differ