Macros are the building blocks of the Warp 10 ecosystem which give it its flexibility and power we have all come to love. But for that love to last security must be taken seriously, and this is what that post is all about, giving useful tips for ensuring safe and smooth executions to your macros.
Macros are a pillar of the Warp 10 ecosystem. They allow WarpScript code to be written in a modular and efficient way, separating the responsibilities between the code crafters and the code users. The power and flexibility of macros are strengths of the Warp 10 platform.
This post aims at listing some OpSec tips which can add security to the mix.
Secure macros
First and foremost it is important to understand that all macros are not equal.
Macros you specify in your own code are different from those loaded from a local or macro repository. Those are slight differences but they are important to know. The first one is that macros loaded from repositories or libraries are marked as secure and as such cannot be SNAPSHOT
ted. Attempting to call SNAPSHOT
on such macros will lead to the following code being output:
<% /* Secure Macro */ %>
The whole point of secure macros is to conceal their actual code so some specific know-how is not directly exposed to users of the macros. Of course, if you have direct access to the repositories themselves this will not prevent you from seeing the code.
In an upcoming release of Warp 10, the MSEC
and MRSEC
functions will be introduced to mark as secure a macro not directly loaded from a repository but generated by such a macro.
The second notable difference is that macros loaded from repositories are named and as such can access elements of the Warp 10 configuration as detailed in the following section.
See more articles about Macros.
Accessing sensitive information
It is quite common to have macros which reside on the server-side which must have access to some sensitive information to perform their duty. Typically tokens fall in that category.
While it could be tempting to store a token in the macro itself, doing so will limit how you can safely distribute your macro and may reduce its flexibility. Indeed, by inserting the token in the macro you restrict the instances of Warp 10 where it can be used, namely only yours!
To circumvent this problem, the MACROCONFIG
function was introduced to allow a named macro to fetch information from the Warp 10 configuration.
When the following code is encountered in a macro called my/test/macro
:
'mykey' MACROCONFIG
The execution environment will look in the configuration if it can find (in this order), any of the following keys:
mykey@my/test/macro
mykey@my/test
mykey@my
If it finds one, then the associated value will be returned by the call to MACROCONFIG
.
This enables you to design macros which will fetch specific information from the Warp 10 configuration and which can therefore be used from different environments.
The next step in operations security when using sensitive information within a macro is to ensure that this information does not leak outside of the macro where it is used.
While it may not seem obvious how this could happen, have in mind that the number of operations which can be performed during execution of WarpScript code is limited, and if the limit is reached, execution will be aborted and the execution environment left in the state it was at the time of the abortion. This means that by wrapping a call to a macro within a TRY
call and limiting the number of operations it could be possible to end the execution after sensitive information was pushed but before it was used, thus leaking the information outside of a secure macro.
In order to mitigate this risk, the GUARD
function was introduced. It allows running a macro in a guarded environment which will clear parts or all of the state of the environment should an exception be thrown while the macro executes. This may include the stack, variables and registers.
<%
'secret' 'X' STORE
%>
[ 'X' ] // No matter the issue of the macro execution above, the content of X will not leak
GUARD
Ensuring macros integrity
WarpScript is a very flexible language. So flexible that any function can be redefined. It means that a macro created after some functions were redefined will use the redefined versions of those functions and not the original one.
In the following example, the macro execution will output 42 instead of doing nothing:
<% 42 %> 'NOOP' DEF
<%
NOOP
%>
EVAL
This situation can be disallowed by using the REDEFS
before a macro is built or a statement executed. In order to not be tricked by a redefined REDEFS
, its redefinition can be cleared using NULL 'REDEFS' DEF
first, which will either restore the original REDEFS
behavior or mark it as undefined if the configuration warpscript.def.unshadow
is set to true
.
WarpScript has the ability to use macros as parameters. This is very powerful but like all power, it should be used with great responsibility. This is why macro signing was introduced.
This feature allows you to cryptographically sign and verify macros to ensure they were not altered. This can help ensure that a parameter macro is legit and not a rogue macro which could have unwanted side effects.
Macros are signed using the MSIGN
function and the signatures can be later verified using MVERIFY
.
// Macro to sign
<%
NOOP
%>
// Generate a private key
'secp256k1' ECGEN
DROP ECPRIVATE
// Generate macro signature
MSIGN
// Concatenate the signature with the original macro
+
// Verify the signature
MVERIFY
Restricting macro access
We have just covered how macro signing could help you ensure a macro is a legit one. But in some cases, it might be suitable to limit who can use a macro.
The capabilities mechanism was added just for that purpose. It allows attaching capabilities to tokens. Those capabilities can then be registered with the execution environment via the CAPADD
function and their presence checked using CAPCHECK
. A macro can therefore restrict its execution to environments with a specifically known capability.
Ensuring smooth replacements
The power of macros comes in part from the ability to modify them dynamically at execution time. This is what DEREF
and ASREGS
do for example, as detailed in this post.
In some cases where a macro is generated by another macro on the server side, it is common to forget that the generated macro is built once and that the same instance is returned each time the enclosing macro is called. This means that any call to DEREF
or ASREGS
on this generated macro will really modify the unique copy of the macro, which is probably not what you expect.
In order to do what you wanted in the first place, you should call SNAPSHOT
followed by EVAL
on the macro before returning it. This will create a clone of the original macro which can be modified without impacting the original macro as the example below shows:
<% $a %> 'macro' STORE
$macro { 'a' 42 } DEREF EVAL // outputs 42
$macro { 'a' 43 } DEREF EVAL // also outputs 42 since the original macro no longer contains $a but 42
<% $a %> 'macro' STORE
$macro 1 SNAPSHOTN EVAL { 'a' 42 } DEREF EVAL // outputs 42
$macro 1 SNAPSHOTN EVAL { 'a' 43 } DEREF EVAL // outputs 43 since $macro was unmodified
Takeaways
Macros are one of the most powerful concepts available in Warp 10. But this power comes with possible security issues which need to be addressed in some sensitive environments. You now have knowledge of the mitigation techniques for the most important issues.
We hope this can help you build safe and efficient macros in the future.
Read more
Share your WarpScript macros
Introducing WarpFleet Synchronizer
Warp 10 beyond OSS
Co-Founder & Chief Technology Officer