Azure / Azurite

A lightweight server clone of Azure Storage that simulates most of the commands supported by it with minimal dependencies
MIT License
1.83k stars 325 forks source link

UserDelegationKey fails with 500 #2420

Open NaridaL opened 4 months ago

NaridaL commented 4 months ago

Which service(blob, file, queue, table) does this issue concern?

blob

Which version of the Azurite was used?

$ azurite --version
3.31.0

Where do you get Azurite? (npm, DockerHub, NuGet, Visual Studio Code Extension)

npm

What's the Node.js version?

node --version v20.12.1

What problem was encountered?

Try to generate user delegated key, see log below.

Steps to reproduce the issue?

If possible, please provide the debug log using the -d parameter, replacing \<pathtodebuglog> with an appropriate path for your OS, or review the instructions for docker containers:

2024-07-02T15:12:11.124Z     info: Azurite Blob service is starting on 127.0.0.1:10000
2024-07-02T15:12:11.127Z     info: AccountDataStore:init() Refresh accounts from environment variable AZURITE_ACCOUNTS with value *****
2024-07-02T15:12:11.191Z     info: BlobGCManager:start() Starting BlobGCManager. Set status to Initializing.
2024-07-02T15:12:11.192Z     info: BlobGCManager:start() Trigger mark and sweep loop. Set status to Running.
2024-07-02T15:12:11.192Z     info: BlobGCManager:markSweepLoop() Start next mark and sweep.
2024-07-02T15:12:11.192Z     info: BlobGCManager:markSweep() Get all extents.
2024-07-02T15:12:11.195Z     info: BlobGCManager:start() BlobGCManager successfully started.
2024-07-02T15:12:11.216Z     info: BlobGCManager:markSweep() Got 36 extents.
2024-07-02T15:12:11.216Z     info: BlobGCManager:markSweep() Get referred extents.
2024-07-02T15:12:11.230Z     info: BlobGCManager:markSweep() Got referred extents, unreferenced extents count is 0.
2024-07-02T15:12:11.230Z     info: BlobGCManager:markSweepLoop() Mark and sweep finished, taken 38ms.
2024-07-02T15:12:11.231Z     info: BlobGCManager:markSweepLoop() Sleep for 600000ms.
2024-07-02T15:12:11.235Z     info: Azurite Blob service successfully listens on https://127.0.0.1:10000
2024-07-02T15:12:42.541Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobStorageContextMiddleware: RequestMethod=POST RequestURL=https://localhost/eastus1/?restype=service&comp=userdelegationkey RequestHeaders:{"host":"localhost:10000","x-ms-version":"2024-05-04","accept":"application/xml","x-ms-client-request-id":"b976eac5-a468-40e4-ae8a-4a666a741fe4","x-ms-return-client-request-id":"true","user-agent":"azsdk-net-Storage.Blobs/12.20.0 (.NET 6.0.31; Microsoft Windows 10.0.22631)","authorization":"Bearer <fake token but removed anyway>","content-type":"application/xml","content-length":"94"} ClientIP=127.0.0.1 Protocol=https HTTPVersion=1.1
2024-07-02T15:12:42.541Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobStorageContextMiddleware: Account=eastus1 Container= Blob=
2024-07-02T15:12:42.542Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c verbose: DispatchMiddleware: Dispatching request...
2024-07-02T15:12:42.543Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: DispatchMiddleware: Operation=Service_GetUserDelegationKey
2024-07-02T15:12:42.544Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c verbose: AuthenticationMiddlewareFactory:createAuthenticationMiddleware() Validating authentications.
2024-07-02T15:12:42.545Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: PublicAccessAuthenticator:validate() Start validation against public access.
2024-07-02T15:12:42.545Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: PublicAccessAuthenticator:validate() Getting account properties...
2024-07-02T15:12:42.545Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: PublicAccessAuthenticator:validate() Retrieved account name from context: eastus1, container: , blob: 
2024-07-02T15:12:42.551Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: PublicAccessAuthenticator:validate() Skip public access authentication. Cannot get public access type for container 
2024-07-02T15:12:42.551Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobSharedKeyAuthenticator:validate() Start validation against account shared key authentication.
2024-07-02T15:12:42.551Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobSharedKeyAuthenticator:validate() Request doesn't include shared key authentication.
2024-07-02T15:12:42.551Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: AccountSASAuthenticator:validate() Start validation against account Shared Access Signature pattern.
2024-07-02T15:12:42.551Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: AccountSASAuthenticator:validate() Getting account properties...
2024-07-02T15:12:42.551Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: AccountSASAuthenticator:validate() Retrieved account name from context: eastus1, container: , blob: 
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: AccountSASAuthenticator:validate() Got account properties successfully.
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: AccountSASAuthenticator:validate() Retrieved signature from URL parameter sig: undefined
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: AccountSASAuthenticator:validate() Failed to get valid account SAS values from request.
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobSASAuthenticator:validate() Start validation against blob service Shared Access Signature pattern.
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: BlobSASAuthenticator:validate() Getting account properties...
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: BlobSASAuthenticator:validate() Retrieved account name from context: eastus1, container: , blob: 
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: BlobSASAuthenticator:validate() Got account properties successfully.
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: BlobSASAuthenticator:validate() Retrieved signature from URL parameter sig: undefined
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: BlobSASAuthenticator:validate() No signature found in request. Skip blob service SAS validation.
2024-07-02T15:12:42.552Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobTokenAuthenticator:validate() Start validation against token authentication.
2024-07-02T15:12:42.553Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: BlobTokenAuthenticator:authenticateBasic() Validation against token authentication successfully.
2024-07-02T15:12:42.553Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c verbose: DeserializerMiddleware: Start deserializing...
2024-07-02T15:12:42.556Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c debug: deserialize(): Raw request body string is (removed all empty characters) <KeyInfo><Start>2024-07-02T15:07:40Z</Start><Expiry>2024-07-09T15:12:40Z</Expiry></KeyInfo>
2024-07-02T15:12:42.560Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: HandlerMiddleware: DeserializedParameters={"options":{"requestId":"b976eac5-a468-40e4-ae8a-4a666a741fe4"},"restype":"service","comp":"userdelegationkey","version":"2024-05-04","keyInfo":{"start":"2024-07-02T15:07:40Z","expiry":"2024-07-09T15:12:40Z"},"body":"ReadableStream"}
2024-07-02T15:12:42.561Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c verbose: SerializerMiddleware: Start serializing...
2024-07-02T15:12:42.562Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c error: ErrorMiddleware: Received an error, fill error information to HTTP response
2024-07-02T15:12:42.562Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c error: ErrorMiddleware: ErrorName=Error ErrorMessage=UserDelegationKey.SignedOid cannot be null or undefined. ErrorStack="Error: UserDelegationKey.SignedOid cannot be null or undefined.\n    at Serializer.serialize (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\@azure\\ms-rest-js\\dist\\msRest.node.js:510:19)\n    at serializeCompositeType (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\@azure\\ms-rest-js\\dist\\msRest.node.js:911:50)\n    at Serializer.serialize (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\@azure\\ms-rest-js\\dist\\msRest.node.js:547:27)\n    at serialize (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\dist\\src\\blob\\generated\\utils\\serializer.js:185:36)\n    at serializerMiddleware (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\dist\\src\\blob\\generated\\middleware\\serializer.middleware.js:27:32)\n    at C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\dist\\src\\blob\\generated\\ExpressMiddlewareFactory.js:91:49\n    at Layer.handle [as handle_request] (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\express\\lib\\router\\layer.js:95:5)\n    at trim_prefix (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\express\\lib\\router\\index.js:328:13)\n    at C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\express\\lib\\router\\index.js:286:9\n    at Function.process_params (C:\\Users\\aleonhard\\AppData\\Roaming\\npm\\node_modules\\azurite\\node_modules\\express\\lib\\router\\index.js:346:12)"
2024-07-02T15:12:42.562Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c error: ErrorMiddleware: Set HTTP code: 500
2024-07-02T15:12:42.563Z 9b77d658-d254-40ae-8d39-8a3b85a4e35c info: EndMiddleware: End response. TotalTimeInMS=21 StatusCode=500 StatusMessage=undefined Headers={"server":"Azurite-Blob/3.31.0"}

Please be sure to remove any PII or sensitive information before sharing!
The debug log will log raw request headers and bodies, so that we can replay these against Azurite using REST and create tests to validate resolution.

Have you found a mitigation/solution?

not yet

NaridaL commented 4 months ago

OK, issue seems to be that the claims "tid" and "oid" were missing from by dummy auth token. These should probably be validated as part of the token validation.

For reference here is a TokenCredential which works:

namespace My.Test.Framework;

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
using Microsoft.IdentityModel.Tokens;

public class SelfSignedTokenCredential : TokenCredential
{
    private readonly TimeSpan _expiration = TimeSpan.FromHours(1);
    private readonly string _tenant = "aaaaaaaa-aaaa-aaaa-0000-aaaaaaaaaaaa"; 
    private readonly string _issuer = $"https://sts.windows.net/aaaaaaaa-aaaa-aaaa-0000-aaaaaaaaaaaa/";
    private readonly byte[] _secret = RandomNumberGenerator.GetBytes(32);

    public override async ValueTask<AccessToken> GetTokenAsync(
        TokenRequestContext requestContext,
        CancellationToken cancellationToken)
    {
        return GetToken(requestContext, cancellationToken);
    }

    public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        var audience = requestContext.Scopes.Length > 0 ? requestContext.Scopes[0] : "default-audience";
        audience = audience.Replace("//.default", "/");
        var expires = DateTimeOffset.UtcNow + _expiration;
        var token = GenerateJwtToken(audience, DateTime.UtcNow - TimeSpan.FromMinutes(5), expires.UtcDateTime);
        return new AccessToken(token, expires);
    }

    private string GenerateJwtToken(string audience, DateTime notBefore, DateTime? expires)
    {
        var securityKey = new SymmetricSecurityKey(_secret);
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        return new JwtSecurityTokenHandler().CreateEncodedJwt(
            issuer: _issuer, 
            audience: audience,
            subject: new ClaimsIdentity(new []
            {
                new Claim("oid", "c0ffee00-c0ff-eeee-0000-c0ffee000000"),
                new Claim("tid", _tenant),
            }),
            notBefore: notBefore,
            expires: expires,
            issuedAt: null,
            signingCredentials: credentials);
    }
}
blueww commented 4 months ago

@EmmaZhu

Would you please help to look at this issue?

EmmaZhu commented 2 months ago

Object ID and tenant ID are required to generate a user delegation key, they should always be included in the token credentials to access Azure Storage Service. Azurite's behavior is expected.

NaridaL commented 2 months ago

@EmmaZhu-MSFT, the issue is not that these fields are required, it is that they are not validated as part of the token validation.

When they are missing, the user delegation fails with an internal error rather than a proper error message.

On Thu, 12 Sept 2024 at 03:06, EmmaZhu-MSFT @.***> wrote:

Object ID and tenant ID are required to generate a user delegation key, they should always be included in the token credentials to access Azure Storage Service. Azurite's behavior is expected.

— Reply to this email directly, view it on GitHub https://github.com/Azure/Azurite/issues/2420#issuecomment-2345111656, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACSTYSUJA4FJTCFUVRRTH4TZWDZJTAVCNFSM6AAAAABKHZE4TWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGNBVGEYTCNRVGY . You are receiving this because you authored the thread.Message ID: @.***>

EmmaZhu commented 1 month ago

With a token with invalid tenant id or object id, Azure would return 401 error like following:

HTTP/1.1 401 Server failed to authenticate the request. Please refer to the information in the www-authenticate header.
Content-Length: 414
Content-Type: application/xml
Server: Microsoft-HTTPAPI/2.0
x-ms-request-id: 2932de7b-701e-0051-45e8-05d02c000000
x-ms-error-code: InvalidAuthenticationInfo
WWW-Authenticate: Bearer authorization_uri=https://eastus2euap.login.microsoft.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize resource_id=https://storage.azure.com
Date: Fri, 13 Sep 2024 14:26:14 GMT
Connection: close

<?xml version="1.0" encoding="utf-8"?><Error><Code>InvalidAuthenticationInfo</Code><Message>Server failed to authenticate the request. Please refer to the information in the www-authenticate header.
RequestId:2932de7b-701e-0051-45e8-05d02c000000
Time:2024-09-13T14:26:14.5704756Z</Message><AuthenticationErrorDetail>Signature validation failed. Signature verification failed.</AuthenticationErrorDetail></Error>

401 error is for bearer token challenge logic, which Azurite cannot support. We'd need to discuss on Azurite's behavior when tid or oid is missing.

NaridaL commented 1 month ago

I don't follow your last point. The following file already includes various verifications on the bearer token claims:

It seems to me all that is missing are some checks there to ensure tid and oid are set.

https://github.com/Azure/Azurite/blob/76f626284e4b4b58b95065bb3c92351f30af7f3d/src/blob/authentication/BlobTokenAuthenticator.ts#L96C2-L96C34

EmmaZhu commented 1 month ago

We definitely should check whether tid and oid is set. The above message is just about what kind of message we should report. We'll discuss internally about it, and will update in this issue with any progress.