Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chose a particular security schema for an ExposedThing #299

Open
Tracked by #315
relu91 opened this issue Feb 8, 2021 · 15 comments
Open
Tracked by #315

Chose a particular security schema for an ExposedThing #299

relu91 opened this issue Feb 8, 2021 · 15 comments
Labels
for next iteration Planned or postponed topics for the future Security use case Describes a scenario that may be useful for technical decisions

Comments

@relu91
Copy link
Member

relu91 commented Feb 8, 2021

As referred in #289 (comment):

  • Applications should avoid entirely providing securityDefinitions
  • They (applications) may choose a particular definition using the schema field (globally or at form level)

This issue keeps track of how it would be possible to choose a particular scheme at the application level. One possible solution would be the definition of a proper function that lists all the possible SecurityScheme supported by a particular protocol binding and the runtime.

listSupportedSchemas(): Map<Protocol,SecuritySchemaDefinition[]>

Where Protocol could be the protocol URI scheme (i.e., HTTP,mqtt, ... )

However, I'm sure if it might have some security implications (fingerprinting?).

@zolkis
Copy link
Contributor

zolkis commented Feb 8, 2021

In the ExposedThing use case it is the script that defines the Thing behavior, including the TD stanza referring to local configuration and provisioning, since it is the glue code that uses various libraries in the local system to expose Thing functionality. So IMHO it should have means to affect how security schemes are defined.

In practice, much of that could go to the scripting runtime, but in the ExposedThing case, the script is part of that runtime.

Currently we have ExposedThing as a conformance class inside the WoT Scripting API, but we could also make a separate API for it, to emphasize the different permissions and different place in the solution stack. That would mean splitting the API along the conformance classes (consumed, discovery, exposed) to completely independent APIs. Would that clarify these?

@relu91
Copy link
Member Author

relu91 commented Feb 9, 2021

From the architectural description of a servient, it seems that the scripts run in a sandboxed environment. As such, in principle, ExpsedThing glue code (application script) is not part of the runtime but it is business logic code that should not have unrestricted access to system-wide resources.

Splitting API is a valid approach but I'd prefer to add a new API endpoint. Something like wot.runtime.runtimeRelatedFunctions. It opens the possibility to interact using a special controlled channel with the underlying runtime. It is more or less like a reflection API. Notice that another usecase for runtime related API is the initialization of oAuth flow as described here:

I think that the only way to fulfill the requirements from that use-case is to design an API that demands the servient runtime to ask for user input. Anyhow, let's discuss this after the Note publication.

@zolkis
Copy link
Contributor

zolkis commented Feb 9, 2021

ConsumedThing scripts do run in a sandbox.
ExposedThing scripts may run in a sandbox, but then they need APIs to access local resources like GPIO, PWM, serial, etc. These are out of scope for WoT, therefore we cannot assume they are part of a sandbox defined by a WoT runtime.
In either case, ExposedThing scripts will be run at the same level as other libraries that have HW access, be it in a sandbox or not (but then what is the point of a sandbox for an API that accesses HW).

@danielpeintner
Copy link
Contributor

From the perspective of a script developer it would be ideal if the script itself can register security schemes (and the according checks) for exposed things just like it can register handlers for reading/writing properties and all the other interactions.

Having said that, I doubt this can be properly achieved ... or what's your opinion on that?

@relu91
Copy link
Member Author

relu91 commented Feb 9, 2021

From the perspective of a script developer it would be ideal if the script itself can register security schemes (and the according checks) for exposed things just like it can register handlers for reading/writing properties and all the other interactions.

Achieving this would mean having complete control over the protocol binding implementation. Instead, they could just "choose" among the list of the supported SecuirtySchemes.

I feel that there still not a real consensus on how we should treat application scripts. The servient architecture schema seems to convey that application scripts are totally on a different level:
image

they need APIs to access local resources like GPIO, PWM, serial, etc. These are out of scope for WoT, therefore we cannot assume they are part of a sandbox defined by a WoT runtime.

From the architecture document, all those APIs should be defined as System APIs. Which I agree are out of the scope of our document. Still, we can list requirements about how those should be implemented or accessed from application scripts

My approach to this topic would be striving as much as possible to limit script control over system resources and promote the usage of existing APIs. For example, use Generic Sensor API.

@zolkis
Copy link
Contributor

zolkis commented Feb 10, 2021

The diagram above was defined by us, to reflect the best known intention. However, there is no uniform implementation of this, so at the moment it is not normative.

The Scripting API, being an optional module in WoT, just provides convenience to develop code for discovering, consuming, and on the other hand, exposing Things.

By default, exposing Things is close-to-native territory. It has two main parts:

  • automates generating a TD
  • and wire up the glue code with the rest of the system.

The first one can be included in libraries like node-wot. This can be standardized, even though the TD spec doesn't provide standard ways to generate a TD.

The second one is not defined and not standardized yet, and this is where we have most divergence and confusion. It involves the following:

  • interface to HW APIs like GPIO, I2C, PWM, etc
  • interface to the underlying platform's comms libraries (like HTTP, CoAP, MQTT, etc)
  • interface to the underlying platform's provisioning, configuration, security and SW update system.

There are more or less standard JS libraries for the first two categories.
However, for the latter, there is nothing standard. Therefore we should not strive to standardize that part. It should be left open for the app developer to pull the necessary dependencies to cover this. This puts the ExposedThing apps to system level, as it will require system level permissions. In this sense, the app will need to define the security scheme in a generated TD, and possibly also implement it.

We can take as much as we want to implement from the above in a standardized library, and define/standardize a SW interface to interact with. But this would involve standardizing a whole platform, which has not been successful to this date, and it has not been the objective of the WoT exercise.

So I would say the safest/most generic assumption is that the ExposedThing API belongs to system level. Whatever convenience functionality is carved out through interfaces from this blob will be a subject to standardization later.

@relu91
Copy link
Member Author

relu91 commented Feb 15, 2021

Btw this discussion is related to #298.

@danielpeintner danielpeintner added the for next iteration Planned or postponed topics for the future label Mar 8, 2021
@zolkis
Copy link
Contributor

zolkis commented Apr 26, 2021

To simplify things a bit, let's consider an analogy: let's separate treating ConsumedThing scripts and ExposedThing scripts like if they were run in containers (which is one possible implementation policy).

So, let's assume a developer writes and runs a script that calls wot.consume() in a given environment, let's assume it's a container. From there, remote things can be accessed and operated. The container protects the platform from those things and vice versa, keeps the data contained.
Then, the developer wants to expose a new exposed thing based on the consumed ones.

Of course, a new exposed thing can be done in any ways, for instance written in assembly and run as an executable in the current environment or in a new container, whatever.
However, for this example, let's assume that a new container is spawned, whose environment is defined by the program run in the current container (where we do the consumes). So the developer writes code in the script to assemble a new TD init dictionary from the consumed ones, that may contain multiple security definitions, define properties etc. That TD init should contain all information needed to make the service operable, i.e. the developer needs to know the underlying platform, for instance that it's a Node.js environment, it is known what comms, I/O, and security libraries are used, besides node-wot. Then the script calls thing.expose(). The implementation, running in the consume-container, will do the following:

  1. read the security definitions in the thing init dict
  2. introspect the underlying platform
  3. come up with a supported set of security definitions (for use with later clients who want to consume it)
  4. spawn a new container, set up based on the dependencies figured out from the TD init, generate the bindings
  5. bind the script provided methods for read/write/observe etc (and link to local system APIs)
  6. start serving requests.
  7. whatever part requires user input, the implementation manages that via local system APIs (e.g. present a in input dialog and manage the content).

Steps 5 is the tricky one, since the script will contain local system API calls, and they need system level permissions. Which is the reason we need step 4, i.e. run the exposed thing in another container with more permissions.

My point is that all of this can (and should) be encapsulated by the implementation. The developer only needs general knowledge about typical security definitions on that platform.

I don't like the idea of a reflection API in this case: I am not sure if from security point of view is a wise thing to provide ways to elevate the current container to more powerful permissions, i.e. you should never give permissions to yourself. That should run through the usual ways to externally vet a new container.

[edit] Note that the original script can consume the new Thing without any issues, by first fetching/discovering the TD served by the other container (either directly or via a directory). [/edit]

Of course, the container analogy was just analogy, but also a possible implementation policy.

What am I missing?

@relu91
Copy link
Member Author

relu91 commented Apr 26, 2021

I think that the key point is:

the developer writes code in the script to assemble a new TD init dictionary from the consumed ones, that may contain multiple security definitions, define properties etc.

How am I supposed to provide security definitions if I can't know which one would be supported by the platform? What happens if I choose a not supported schema? Since those definitions are just hints I would imagine this scenario: developers just put every possible schema hoping that one of them is supported by the underlying platform.

My take on this issue is either we provide a way to choose the security scheme or we don't let developers to chose the security scheme 😃.

Sincerely, I think the issue went a little bit out of topic bringing up more profound issues (which are actually really important and interesting) but maybe they should be addressed separately.

@zolkis
Copy link
Contributor

zolkis commented Apr 26, 2021

How am I supposed to provide security definitions if I can't know which one would be supported by the platform?

The assumed developer position is to develop an exposed thing for a given platform, where the things mentioned here are available and known by the developer.

If the TD init contains a security definition that is not supported, it will be ignored unless it is referenced, in which case it will trigger an error.

@zolkis
Copy link
Contributor

zolkis commented Apr 26, 2021

To move it forward, we could start developing a wot-runtime and a wot-provisioning API objects, which would not be available for everyone and in each run mode.

So in this issue the need was

  • to have a wot.runtime.listSecuritySchemes() method
  • to extend the implementations to handle/configure standardized names from that list.

In other words, move to programmatic space the current declarative way to specify security schemes in the init dictionary.

Though there are also advantages in the declarative approach - more discussion is needed.

Then, we need to provision those schemes, which is one deeper/lower layer of API, like
wot.provisioning.addSecurityScheme(...) etc.

@relu91
Copy link
Member Author

relu91 commented Apr 26, 2021

The assumed developer position is to develop an exposed thing for a given platform, where the things mentioned here are available and known by the developer.

Just to be sure are you referring to this when you're talking about things that are known/available?

interface to the underlying platform's provisioning, configuration, security and SW update system.

I would say that it is a reasonable assumption, but are we really sure that it is always the case? I mean the developer should be also the platform deployer/manager to know ahead of time which configuration will be used for its platform. Therefore, possibly as you suggested today the platform and the application should be treated as a single entity -> no runtime. I would love to keep the runtime in our design for the reasons that I explained today. But I also understand your concerns about the fact that we still do have a consistent implementation and it might end up being a task greater than us.

@zolkis
Copy link
Contributor

zolkis commented Apr 26, 2021

But we can extend the discussion to system level APIs like accessing GPIO, PWM, Generic Sensors, etc.
IMHO all that should be a possibility for the developer, not a standardized WoT System API (like WASI + Johnny 5).

In order to figure out what makes sense to standardize and what can be left open for dependency resolution (available in Node.js), at the beginning let's just start with coding through a full ExposedThing solution, figure out what external libraries can we use for different functionality, what is common, what makes sense to standardize etc.

It is quite clear we do need to standardize some names/vocabulary, which is needed both for the declarative and programmatic way to initialize az ExposedThing, but that is quite close to being standardized.

@relu91
Copy link
Member Author

relu91 commented Apr 26, 2021

It is quite clear we do need to standardize some names/vocabulary, which is needed both for the declarative and programmatic way to initialize az ExposedThing, but that is quite close to being standardized.

Yup at least in the lastest period we are discussing a lot about this, thanks also to the ThingModel. I think we could achieve a standardized approach soon.

I'll try to provide a concrete piece of code using this newly proposed method. So let's assume these two hypothesis:

  1. I want to use oAuth 2.0 but I could leave with simple bearer tokens. I don't accept another method
  2. I do not want to configure any of the two SecuritySchemaDefinition. I trust the default settings of the runtime
  let partialTD = {
    properties: {
      temperature: {/* definitions */}
    }
  };
  /*
  wot.runtime.securityDefintions returns an Array with
  the "preaviously" configured securityDefinitions. For example:
  [
  { 
    "scheme" : "oauth2"
     ....
  },
  { 
    "scheme" : "basic"
     ao Da
  },
  ]

  */
  const oAuth2Supported = wot.runtime.securityDefinitions.filter( 
                                scheme -> scheme.name === "oauth2");
  const bearerSupported = wot.runtime.securityDefinitions.filter( 
                                scheme -> scheme.name === "bearer");
  if(!oAuth2Supported && !bearerSupported){
    throw new Error("Sorry no security schema matches my standards")
  }
  
if(oAuth2Supported){
    partialTD.securityDefinitions = {
      "oauth2_sec" : oAuth2Supported 
    }

    partialTD.security = "oauth2_sec"
  }else{
     partialTD.securityDefinitions = {
      "bearer_sec" : bearerSupported  
    }

    partialTD.security = "bearer_sec"
  }
  
  let thing1 = await WOT.produce(partialTD);
  // initialize Properties
  await thing1.writeProperty("temperature", 0);
  // add service handlers
  thing1.setPropertyReadHandler("temperature", () => {
     return readLocalTemperatureSensor();  // Promise
  });
  // start serving requests
  await thing1.expose();

We could start from here and improve. I think to assign the securityDefinition found in the global list is kind of redundant but it might be necessary for complex scenarios where I want to assign a particular scheme just for some properties. Anyhow, next TD versions will allow assigning the securityDefinition directly to scheme so I think this process is reasonable. What do you think?

@zolkis
Copy link
Contributor

zolkis commented Apr 26, 2021

Thanks, this is helpful, now we are talking in a more clear way. :)
OK, this splits the work to defining/implementing and using security definitions, therefore allowing reusing some of the scripts where the same APIs and standardized data representations are available. Right now these are done as part of the implementation.

This can be done in the way presented above, which is using programmatic API for fetching the security descriptions (and tied to more powerful permissions and API entry point), and a declarative API to tell them to the ExposedThing implementation.

Another (theoretical) approach could be a programmatic API at ExposedThing level as well, like it is done with request handlers:
thing.addSecurityDefinition({ ... }). However, I think the above approach makes more sense, both from developer/code flow and standardization points of view.

I also like the API design for (optional) filtering. I guess the getter would provide a full list. If it has to be an async API, we could use chaining. To be discussed. We could also use the to be agreed filtering scheme with discovery as well.

Also related to other issues, the question is where should we place this API. We should re-discuss splitting the WoT API into API objects/namespaces and permission levels. Please check out the Permission API. Today I would use the following grouping, requiring more and more powerful permissions:

  • client APIs: discover(), consume() (client level interoperable code)
  • server APIs: produce(), runtime.getSecurityDefinitions(), (server high level interoperable code)
  • elevated runtime functionality, e.g. script management (server low level interoperable code, could be merged with the one above, TBD)
  • provisioning APIs: bootstrap, provision security, configure (e.g. add security definitions).

These could be used from the same script, but requiring different permissions.
Conformance classes can be finer grained, e.g. we could have all these in separate conformance classes, allowing optional implementation of all this, with feature detection.

@mmccool mmccool added the security-tracker Group bringing to attention of security, or tracked by the security Group but not needing response. label Dec 13, 2021
@danielpeintner danielpeintner removed the security-tracker Group bringing to attention of security, or tracked by the security Group but not needing response. label Oct 3, 2023
@relu91 relu91 added the use case Describes a scenario that may be useful for technical decisions label Jan 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for next iteration Planned or postponed topics for the future Security use case Describes a scenario that may be useful for technical decisions
Projects
None yet
Development

No branches or pull requests

4 participants