Search code examples
javaazureazure-cosmosdbazure-cosmosdb-gremlinapi

Azure Cosmos DB Gremlin/Tinkerpop Token Auth with Java SDK


I'm trying to connect to a Gremlin collection in Azure Cosmos DB using a resource token. I adapted the documentation from here (it's for C# mainly): https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-use-resource-tokens-gremlin

The issue is that the date header of the token seems to be invalid once I try to access the data:

Exception in thread "main" java.util.concurrent.CompletionException: org.apache.tinkerpop.gremlin.driver.exception.ResponseException: 

ActivityId : 00000000-0000-0000-0000-000000000000
ExceptionType : UnauthorizedException
ExceptionMessage :
    The input date header is invalid format. Please pass in RFC 1123 style date format.
    ActivityId: 755ab024-fc79-47a3-bc44-3231b2db7dc1, documentdb-dotnet-sdk/2.7.0 Host/64-bit MicrosoftWindowsNT/6.2.9200.0
Source : Microsoft.Azure.Documents.ClientThe input date header is invalid format. Please pass in RFC 1123 style date format.
ActivityId: 755ab024-fc79-47a3-bc44-3231b2db7dc1, documentdb-dotnet-sdk/2.7.0 Host/64-bit MicrosoftWindowsNT/6.2.9200.0
    BackendStatusCode : Unauthorized
    BackendActivityId : 755ab024-fc79-47a3-bc44-3231b2db7dc1
    HResult : 0x80131500

Anyone knows how to fix that? The JVM is set to GMT via -Duser.timezone=GMT

Here's the code. Please note that it's a Java CLI application just for testing connectivity. All data of the cfg is basically given by cli, method names should be self-explanatory.

Token generation, this is using the master key for the DocumentClient instance:

...
import com.microsoft.azure.documentdb.DocumentClient;
import com.microsoft.azure.documentdb.DocumentClientException;
import com.microsoft.azure.documentdb.FeedResponse;
import com.microsoft.azure.documentdb.Permission;
import com.microsoft.azure.documentdb.PermissionMode;
import com.microsoft.azure.documentdb.ResourceResponse;
import com.microsoft.azure.documentdb.User;
...

public class TokenGenerator {

    private String USER_ID = "demo-1";

    public String generateToken(CmdLineConfiguration cfg) throws DocumentClientException {
        try (DocumentClient client = Utilities.documentClientFrom(cfg)) {
            String databaseLink = String.format("/dbs/%s", cfg.getDatabaseId());
            String collectionLink = String.format("/dbs/%s/colls/%s", cfg.getDatabaseId(), cfg.getCollectionId());

            // get all users within database
            FeedResponse<User> queryResults = client.readUsers(databaseLink, null);
            List<User> onlineUsers = queryResults.getQueryIterable().toList();

            // if a user exists, grab the first one, if not create it
            User user;
            Optional<User> onlineUser = onlineUsers.stream().filter(u -> u.getId().equals(USER_ID)).findFirst();
            if (onlineUser.isPresent()) {
                user = onlineUser.get();
            } else {
                User u = new User();
                u.setId(USER_ID);
                ResourceResponse<User> generatedUser = client.createUser(databaseLink, u, null);
                user = generatedUser.getResource();
            }

            // read permissions, if existent use, else create
            FeedResponse<Permission> permissionResponse = client.readPermissions(user.getSelfLink(), null);
            List<Permission> onlinePermissions = permissionResponse.getQueryIterable().toList();
            Permission permission;
            if (onlinePermissions.size() == 0) {
                Permission p = new Permission();
                p.setPermissionMode(PermissionMode.Read);
                p.setId(USER_ID + "_READ");
                p.setResourceLink(collectionLink);
                ResourceResponse<Permission> generatedPermission = client.createPermission(user.getSelfLink(), p, null);
                permission = generatedPermission.getResource();
            } else {
                permission = onlinePermissions.get(0);
            }
            // return the token
            return permission.getToken();
        }
    }
}

Connect and query Gremlin:

...
import org.apache.tinkerpop.gremlin.driver.AuthProperties;
import org.apache.tinkerpop.gremlin.driver.AuthProperties.Property;
import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Cluster;
import org.apache.tinkerpop.gremlin.driver.ResultSet;
...


        Cluster cluster;
        String collectionLink = String.format("/dbs/%s/colls/%s", cfg.getDatabaseId(), cfg.getCollectionId());
        TokenGenerator tg = new TokenGenerator();
        String token = tg.generateToken(cfg);

        Cluster.Builder builder = Cluster.build(new File("src/remote.yaml"));
        AuthProperties authenticationProperties = new AuthProperties();
        authenticationProperties.with(AuthProperties.Property.USERNAME, collectionLink);

        authenticationProperties.with(Property.PASSWORD, token);
        builder.authProperties(authenticationProperties);
        cluster = builder.create();

        Client client = cluster.connect();
        ResultSet results = client.submit("g.V().limit(1)");

        // the following call fails
        results.stream().forEach(System.out::println);

        client.close();
        cluster.close();
    }

src/remote.yml

hosts: [COSMOSNAME.gremlin.cosmosdb.azure.com]
port: 443
username: /dbs/DBNAME/colls/COLLECTIONNAME
connectionPool: { enableSsl: true}
serializer: { className: org.apache.tinkerpop.gremlin.driver.ser.GraphSONMessageSerializerV2d0, config: { serializeResultToString: true }}

Solution

  • With Azure Support I found out that the issue was a bug on Azure side. It occurres when using custom VNets together with Cosmos. Meanwhile Azure has fixed it and everything is working.