absmach / magistrala

Industrial IoT Messaging and Device Management Platform
https://www.abstractmachines.fr/magistrala.html
Apache License 2.0
2.48k stars 674 forks source link

Implement Personal Access Tokens (PATs) #2048

Open dborovcanin opened 1 year ago

dborovcanin commented 1 year ago

API keys will be used for communication between services. This can be done even as a separate service, but we'll first need some investigation and specs before implementing it. @arvindh123 Please write all the findings in the comment section of this issue.

dborovcanin commented 6 months ago

@arvindh123 Please link here comments from the initial implementation you've done.

arvindh123 commented 5 months ago

API Keys which here mentioned are Personal Access Token

PAT have scopes, PAT might or might not have full access to Magistrala. A Magistrala user can create PAT with limited scopes like Example: PAT with scopes of read only things, users, domains. This PAT could not create any entities.

PAT are helps to run automation scripts without user interventions. We could not use password for automations scripts, because If authentication system have Multi-Factor authentication, then it needs human intervention, which becomes interruptions for automation scripts.

If PAT is compromised, we can simply revoke it and create new . PAT might not have full access. So with compromised PAT (PAT with limited scope) no one could not do much things like beyond the scope mentioned in PAT, Example if a compromised PAT is created with read only access, then no one could not do operations like create or delete.

arvindh123 commented 4 months ago

PAT Data structure

{
    "platform": {
        "users": {
            "create": {},
            "read": {},
            "list": {},
            "update": {},
            "delete": {}
        }
    },
    "domains": {
        "domain_1": {
            "entities": {
                "groups": {
                    "create": {}, // this for all groups in domain
                },
                "channels": {
                    // for particular channel in domain
                    "delete": {
                        "channel1": {},
                        "channel2":{}
                    }
                },
                "things": {
                    "update": {} // this for all things in domain
                }
            }
        }
    }
}
arvindh123 commented 4 months ago

I'm trying to explain here challenges with example use case

Lets take things share function, It requires Token for Authn and Authz.

The PAT will be passed Bearer Token via header. The service function input variable token will either access token or PAT

Typically, for access token , grpc function Identify returns user id , domain id , subject id. These ids are required for further process.

In auth Identify grpc call we can verify the PAT originality. But in response we can return only user ID. Because PAT are not specific to domain, it is specific to user. A PAT might contains scopes for multiple domains.

So service function could not determine on which domain request need to process. The Authorization grpc call needs domain id to do authorization.

In service function, input variable id provide the thing ID. Using this thing ID we can find the PAT have access that thing ID and domain ID. But down side, since we don't know the domain ID , So we need to iterating over all domains in PAT to find. This is not efficient process.

// Token - Access token / PAT encoded ID
func (svc service) Share(ctx context.Context, token, id, relation string, userids ...string) error {
    user, err := svc.identify(ctx, token) // grpc identify call
    // users = { user id , pat id } -> response for PAT,  missing domain ID
    if err != nil {
        return err
    }

    if _, err := svc.authorize(ctx, user.GetDomainId(), auth.UserType, auth.TokenKind, user.GetId(), auth.DeletePermission, auth.ThingType, id); err != nil {
        return errors.Wrap(svcerr.ErrAuthorization, err)
    }

    policies := magistrala.AddPoliciesReq{}
    for _, userid := range userids {
        policies.AddPoliciesReq = append(policies.AddPoliciesReq, &magistrala.AddPolicyReq{
            SubjectType: auth.UserType,
            Subject:     auth.EncodeDomainUserID(user.GetDomainId(), userid),
            Relation:    relation,
            ObjectType:  auth.ThingType,
            Object:      id,
        })
    }
    res, err := svc.auth.AddPolicies(ctx, &policies)
    if err != nil {
        return errors.Wrap(errAddPolicies, err)
    }
    if !res.Added {
        return err
    }
    return nil
}

Options:

ToDo:

arvindh123 commented 4 months ago

@dborovcanin If we add domainID to all the endpoints like /domains/{domainID}/things/{thingid}/share Then we need to modify the addons endpoints , since they also works on domain level. And also we need to extend PAT for Addons services. I hope with modifying auth function (make it work with PAT) we can make it work for all services including addons

arvindh123 commented 4 months ago

Embedded KV Store

arvindh123 commented 4 months ago
arvindh123 commented 4 months ago

I'm leaning towards Badger, It looks promising for me. Badger’s design is based on a paper titled WiscKey: Separating Keys from Values in SSD-conscious Storage.

And we can implement distribute Badger with Raft as mentioned in previous comment

dborovcanin commented 3 months ago

We will need this for Dashboards sharing on our UI.

dborovcanin commented 2 months ago

This is blocked by the new Auth model, but it is also blocking dashboards sharing on the UI side, so we must address it in this sprint.

dborovcanin commented 1 month ago

This ticket is becoming urgent due to plans to use PAT in Dashboard sharing and other projects that utilize MG auth services.