Keeping keys "safe" in a different way

When using other systems, through a provided API, our pico will need to identify itself in some way. Such identifiers need to be presented by the pico when the outside service is needed, but they should not be hard-coded in the KRL source code of the ruleset.

The classic way to accomplish this is through the use of a Key Module, and that method works just as well in the node pico engine.

This document describes a different approach.

Instead of hard-coding the identifiers in a special KRL module, we can expect the operator of the pico to provide the information, and store it in an entity variable. Consider this before and after example, with the "before" case taken from another cookbook page. The ruleset from that page is repeated here for convenience:

ruleset wovyn_co2_recorder {
  global {
    url = "https://script.google.com/macros/s/AKfycby7bk...j0oVR_yn-u/exec"
  }
  rule record_co2_level_to_sheet {
    select when wovyn co2_level_recorded
    pre {
      ts = time:strftime(event:attr("timestamp"), "%F %T");
      data = {"timestamp": ts, "concentration": event:attr("concentration")}
    }
    http:post(url,qs=data) setting(response)
    fired {
      ent:lastData := data;
      ent:lastResponse := response;
      raise wovyn event "co2_level_recorded_to_sheet" attributes event:attrs()
    }
  }
}

The problem here is that the URL of the external service (in this case a google macro) is hard-coded (line 3), and it contains an identifier that needs protection (redacted here), so that this ruleset should not be checked in to a public repository.

Instead of having the URL in clear text, bound to an identifier, we'll want to store it in an entity variable. The solution is shown here:

ruleset wovyn_co2_recorder {
  meta {
    shares __testing
  }
  global {
    __testing = { "queries": [ { "name": "__testing" } ],
                  "events": [ { "domain": "co2_recorder", "type": "new_url", "attrs": ["url"] } ] }
  }
  rule set_up_url {
    select when co2_recorder new_url url re#^(http.*)# setting(url)
    fired {
      ent:url := url;
      ent:urlInEffectSince := time:now()
    }
  }
  rule record_co2_level_to_sheet {
    select when wovyn co2_level_recorded where ent:url
    pre {
      ts = time:strftime(event:attr("timestamp"), "%F %T");
      data = {"timestamp": ts, "concentration": event:attr("concentration")}
    }
    http:post(ent:url,qs=data) setting(response)
    fired {
      ent:lastData := data;
      ent:lastResponse := response;
      raise wovyn event "co2_level_recorded_to_sheet" attributes event:attrs()
    }
  }
}

The record_co2_level_to_sheet rule (lines 16-28), is guarded by a where clause (line 17), so it will not be selected until the pico/ruleset has a value for ent:url. It uses the entity variable instead of the global name (line 22). Otherwise, it is unchanged.

This means that the operator can install the ruleset into the pico without a problem, even though the wovyn:co2_level_recorded event might be being raised in the pico periodically. In other words, this ruleset can be installed into an actively working pico, whose behavior won't change until the operator provides the URL to the pico.

How does the operator provide the URL value (which includes identifiers that need to be secret)? By sending the co2_recorder:new_url event with the URL as the value of the url event attribute. Notice that the Testing tab is a convenient way to do this, because the (shared) __testing map contains an entry for this event/attribute in line 7.




Copyright Picolabs | Licensed under Creative Commons.