home-assistant / architecture

Repo to discuss Home Assistant architecture
319 stars 100 forks source link

Users & Authentication #17

Closed fanthos closed 6 years ago

fanthos commented 6 years ago

We have HTTP api support in HA, but currently only has password auth supported.

If password leaked accidently, we have to change the API password on HA and every device that using the API with password.

I think we should have more authentication method, like public key and HMAC, to prevent use same password everywhere. Also, we can also support multiple user pass pair.

balloob commented 6 years ago

So I think that this is indeed part of a bigger story: users, authentication and authorization (permissions). We should think of solutions that are capable of all yet can be implemented 1 piece at a time.

fanthos commented 6 years ago

According to django.contrib.auth.backends, I think its not hard to make them compatible mostly, but hard to use their auth providers because seems providers may use django api.

AWS4 looks a good reference for HMAC implement. ref https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

fanthos commented 6 years ago

Entity id residents in json data, so I think per-entity permission should be the hard part of the issue. But access by service name should not be that hard.

Currently user auth status accessible in View by request[KEY_AUTHENTICATED], and it cannot be accessed elsewhere, so if we want to add multi-user support, the username should be somewhere better than the request object..

The pluggable authentication seems relative easy to implement.

balloob commented 6 years ago

For MVP, let's limit to HTTP Views. Going down the rabbit hole of per entity permissions and many things will require a major refactor. It requires passing auth along with service calls (as we don't know which entity it will impact until it is processed). And yes, that does mean that allowing access to the call service permission is way too broad, but that should be solved later.

pvizeli commented 6 years ago

We need also support the old token system because there are a lot scripts/3-party application they work with this, maybe only for the API.

fanthos commented 6 years ago

Permission per view sounds good. We can add special code on service call to make it works with permission. The token system could also "pluggable" by using something like username:token.

I think the auth could become a platform.

ps. We have too many platforms, maybe some platform can grouped into hub platform?

balloob commented 6 years ago

I think that you're confusing what is a component and a platform: https://home-assistant.io/developers/architecture_components/

Also, I don't think that we have too many components. Components organize things, platforms implement things. Components will take care of setting up the connection, maybe some extra services. Seems just fine.

The existing authentication via a token can be provided as one of the pluggable backends. Auth should not be a component or a platform. Authentication and authorisation is a core part of Home Assistant. It's a foundational functionality. I would expect them to live in homeassistant/auth/ folder.

fanthos commented 6 years ago

I agree that auth should in core part of Home Assistant, but some other core feature like http system_log already in homeassistant/components. Seems easier to make it pluggable if it is a component.

Currently we have 150+ *.py in homeassistant/components/.

akatrevorjay commented 6 years ago

It would be awfully nice to have nice syntactic sugar for HTTP views ala simple RBAC that is handed an actual authenticator that provides a raw interface. We'd need to expose required inputs since it varies per ofc, such as otp needing a token, but thinking ahead, this will vary quite a bit, ie oauth2, krb, u2f, etc.

@fanthos I totally agree if we can just reuse an existing framework's API and get what it comes with for free, that sounds amazing.

Thinking way far ahead: Having an optional namespace (could even just be a specific role now that I think about it) would allow dynamic registrations inside of a component. This could provide a safer haven for things like the credentials that huemulation generates, which I quite like the thought of as currently it's kind of interspersed with variable data iirc. (I think this may matter more so in my toy branch; I started adding in v2 huemulation, but it's not complete yet.)

Since we're a web app, being dynamic like this would allow us handle token generation much smoother for services as well. I'm unsure how many of our components are actually oauth tokens though, only a couple come to mind.

Personally, I want to make a libsecret/keyring backend.

Edit: I see per entity was talked about above and I missed it in my first pass, so ignore the redundant paragraph if you don't mind ;)

akatrevorjay commented 6 years ago

Yeah, since since this will, at least eventually, become part of the API that is provided to the individual device/platform modules, I don't know where else it could even go but core, eh?

IMO, preferably the pluggable parts (ie auth backends) wouldn't rely on being part of the homeassistant namespace at all, but maybe you have different thoughts on that. Personally, I find namespaced modules in Python to be cludgy at best.

Just thinking aloud here,how everyone else declares a list of middleware classes is simple and works quite well; meaning a list of class import paths. Of course, simple aliases could be exposed to avoid typing the absolute path for internal backends (like we're used to for the rest of the configuration). Handling it as a list would allow your auth to be "stacked", which is what you'd want most of the time for things such as 2 factor, no?

If we wanted to make selection of backends very flexible, PAM is a fantastic example, although I'm sure we could get configuration much more intuitive these days. That actually makes me think that we'd get a ton of wanted integrations via a single pam authenticator.

Thoughts?

fanthos commented 6 years ago

Basic features should in core, like password authentication, permission, built-in group(role), built-in user.

I think using auth component could make the backend pluggable easier. We can add new auth backend by simply put a file to config dir.

akatrevorjay commented 6 years ago

Being able to pip install it seems like it may be a win to me at least since you can keep it up to date just alike any other package, py deps (even on >= version of hass that supports it) can then handled as well. I could be missing something however, I know python packaging is rather daunting but a lot is gained from it as well (tests! it's a trade off I suppose, I'm not pushing hard either way, just talking this out and making sure we're thought of any corner cases here.

I don't think most users would even use this, but anyone developing their own could then distribute it without forking hass (if it were possible that is).

Oh, actually, let me ask you this, what kind of file do you mean in the config dir? If you mean a configuration file (ie not python), then ignore :)

While it may end up just fine and perfect, it scares me if it's a python file, as that comes with it a wide range of consequences that hinder usability later.

JensTimmerman commented 6 years ago

My wishlist:

unrelated to this change, but also important:

I am willing to do some coding work on this if an architectural change is approved.

OttoWinter commented 6 years ago

My 2 cents:

JensTimmerman commented 6 years ago

@OttoWinter instead of saving pw plain text you could save it hashed:

As an upgrade option you could add and use a new config option api_password_hash and save it like salt:hex_password_hash

def validate_password_hash(api_password_hash, password):
    """Test if password is valid based on a given password"""
    password_hash, salt = api_password_hash.split(':')
    hashed_pw = hashlib.pbkdf2_hmac('sha256', bytes(password, 'utf-8'), bytes(salt, 'utf-8'), 100000)
    hashed_pw = binascii.hexlify(hashed_pw)
    return hmac.compare_digest(hashed_pw, password_hash)

The remember option should then not store this hash or the plaintext password, but a timestamped proof of authentication, but this get's pretty technicall fast.

In the long run it would probably make more sense to having an on boarding process where users and passwords stored hashed in a database next to tokens/sessionids using existing (or to be developed?) aiohttp middleware (see also things like https://aiohttp.readthedocs.io/en/stable/web_quickstart.html#user-sessions )

I do agree on the proxy for https, but we should probably document how to do this and make it very easy to do and show at least a one time reminder on how to set it up at startup when not running on https + check to see if websockets are also on wss so not just frontend is on https but websockets go to unsecured connection.

balloob commented 6 years ago

Yes, passwords will be stored hashed. The only reason we currently don't have it, is because we don't really have a security layer. That's what we're trying to establish here 😉

Ideally, no configuration.yaml is involved for changing passwords. It's all done via the Frontend or CLI (via the same API as frontend). Just like when you setup a new computer, we ask for a username and password and make that the admin account. (this fits with my bigger plans of an onboarding wizard including discovery/config entries etc). Configuration.yaml can be used to configure auth systems and if a user wants, configure an auth system that is just a list of user/pass combo's.

We can't just copy paste the Django system and use it. We can use it as inspiration to see what kind of things we need to think of.

I would expect our auth system to be modeled after OAuth 2:

Auth will live inside homeassistant.auth.*. Will be able to specify REQUIREMENTS just like components/scripts/platforms so they can pull in connectors for active directory or whatever crazy things.

The only thing that we need to consider, is that if we allow authentication, we also need to think about registration. And if we do registration, we need user profile page to allow people to manage their password/totp (if allowed by authentication). All these things can however be postponed. We can literally have our first auth be a list of hardcoded user/pass combos

homeassistant:
  auth:
    - platform: bla
      users:
        - username: balloob
          password: bla
akatrevorjay commented 6 years ago

@OttoWinter I absolutely agree with dropping SSL support in hass. I tried to ignore that it's even in the options list to begin with. Just run it with letsencrypt and nginx; agreed.

Also agree with everything else you said, for that matter, except for storing passwords plaintext, a hash is more appropriate.

akatrevorjay commented 6 years ago

Instead of modeled after oauth2, why not use oath2 @balloob ? I'm pretty sure it supports everything we need, no?

balloob commented 6 years ago

Here is my proposal for an MVP for users and permissions. It's the bare minimum (but still big).

https://docs.google.com/drawings/d/1ciudYSgf973T6Tx9DBp7g4KHtCB6wL7bVSV__xXfqr4/edit?usp=sharing

fanthos commented 6 years ago

I think HTTP should also have HMAC support, not sure how to use it with oauth.

balloob commented 6 years ago

Password hashing algorithms are the responsibility of the auth provider.

emlove commented 6 years ago

Can we have the auth providers use some form of the config flow manager? That's already got support for user input, complex flows such as OAuth, 2FA, and even includes schema verions. Especially since this is net new, we can enforce they all use it from the beginning.

balloob commented 6 years ago

Yes, that is a great idea. We should do that.

NovapaX commented 6 years ago

Has there been thought about a 'recovery' path? (I think a safe but accessible way of access recovery is necessary in the case of a Hass.io box) Maybe some sort of recovery-token for a user can write down or store somewhere?

I recon that recovery could just be one of the auth_providers but still... just making sure this won't be forgotten ;)

balloob commented 6 years ago

Making recovery an auth provider sounds like an excellent idea. I think that we might also provide some command line tools to wipe all users and create a new user.

joydashy commented 6 years ago

Very excited this will receive more love now balloob found a new home at Unifi!

PS. I also agree that HA should drop HTTPS support, leave that to Nginx. HA should not be directly exposed to the internet.

balloob commented 6 years ago

We're not going to drop HTTPS support.

fanthos commented 6 years ago

I think it should be great if HASS can act as OAuth provider. Also, I think most HTTP API(except /api/stream and /api/websockets/) should change to "universal" API, it should accessible using WebSockets.

madjam002 commented 6 years ago

@balloob Is there a reason why you wanted to go down the route of a custom auth provider API rather than using something like OpenID connect? It would then be possible to plug in custom auth providers without any code changes.

balloob commented 6 years ago

I did not consider OpenID connect because it didn't show up when I did my research. We ended up with OAuth 2 and cherry-picked the client ID approach from IndieAuth. Eventually we might explore adopting more from the indieauth spec.

madjam002 commented 6 years ago

IndieAuth looks cool, will check it out! I might have a play around with OpenID Connect as an auth provider at some point, would be nice to hook Home Assistant up to an Identity Provider such as Active Directory or FreeIPA/OpenLDAP which can be quite common in home lab environments

balloob commented 6 years ago

That is possible with the current architecture. One problem we have with IndieAuth is that it requires domain names while we run mainly local, having only IP or hostnames. That's why we stuck with just taking the client ID approach from it.