golden-demise

Designing a policy language prototype

I've been working with Terraform for a couple of years now, as well as a couple of compliance languages that Terraform Cloud/Enterprise products support (Open Policy Agent and Hashicorp Sentinel).

Generally, these are implemented in very large organizations that are advanced users of Terraform. The learning curve is significant, particularly if you are not both a developer and well-versed in Terraform JSON plan structure.

I thought it would be an interesting and entertaining project to prototype a policy language that uses extremely simple phrasing that is completely human readable (and by extension writable).

A simple policy might read something like: “All awsebsvolume must have tag foo: bar” or “Any awscloudtrail must have includeglobalserviceevents set to True”

I ultimately decided to use Raku lang to create my prototype, primarily because I enjoy writing in it and thought this would be a good project with few requisite libraries (which aren't always full featured/maintained) and potentially a good use case for Grammars (once I actually figure out how to use them, my initial forays into the documentation have not been fruitful)

The initial goal is to have a simple evaluation of a single policy manually invoked against a JSON planfile, which will print violations.

After wrestling with .grep syntax for awhile, I came up with a very simple initial subroutine to let me poke around the plan structure:

use JSON::Fast;

my $plan = from-json "example-plan.json".IO.slurp;
my $resource-changes = $plan<resource_changes>;

# There's no reason to expect that non-managed resources will be needed.
sub get-resource-type (@resource-list, Str $resource-type, Str $mode="managed") {
    @resource-list.grep: { $_<mode> eq $mode && $_<type> eq $resource-type } 
}

say get-resource-type($resource-changes, "aws_ebs_volume");

I've since added more LOC and made things worse. However, I look forward to getting a minimum prototype working and then breaking things out into modules/classes.

Messaging is an eternal problem. Most people tend to have a preferred specific messenger which can make communicating a bit of a hassle, with the breadth and depth of various services out there.

Some popular services include Telegram, Signal, Messenger, Whatsapp, IRC, Matrix, XMPP and Discord. Almost all of these require a separate application and configuration, in addition to a separate client.

Personally, I'm a bit of a pragmatist with communication. I am happy to go where my friends are, even if their chosen platform strongly goes against the things that I consider important in such a platform.

One approach is to essentially iframe everything into a single master web client, which is the approach that Franz takes.

I didn't fully review this product, the free version was too limited for my purposes.

My current favored solution is based on the implementation that Beeper generously maintains. This product bridges all of your communications together for $10/month. It combines a series of Matrix bridges with a beautifully customized fork of the SchildiChat Matrix client. They are currently accepting registrations on a waitlist basis- I've been on the waitlist for years but haven't been able to try it out yet. Given the amount of engineering overhead and server resources required for Synapse, I'm very curious how they approach these challenges.

Thankfully they have an open source ansible script they maintain that allows you to spin up your own instance of this Matrix based service:

https://github.com/spantaleev/matrix-docker-ansible-deploy

While you can't use their customized client, it gives simple instructions for installation and configuration (it is fairly involved, and would be rough if you're not technically inclined) via an ansible playbook, which makes managing and administrating your installation a breeze.

The default Synapse (a Matrix server implementation written in python) server is not particularly resource efficient. It is possible to install on a VPS with 2GB of ram, but I'd recommend 4GB as Synapse is memory hungry, particularly if you use Matrix itself for anything outside of bridging to other services.

The cost of self hosting depends on your VPS provider- a 4GB VPS on racknerd costs less than $45 per year when they are on special. You will want a domain name as well if you do not already have one.

I've been running my own Fediverse instance for a few months now, and have some data to share around costs and resource utilization.

If you're just getting interested in the Fediverse, hosting your own server is typically not the best way to get started. You'll want to try out some of the existing servers and technologies to understand the type of community you wish to participate in, and how you prefer to interact with the broader Fediverse. This post will assume you've already done your research and have decided to start your own Pleroma server (a popular alternative is Mastodon).

Pleroma is known for being lightweight and fast- it also requires few resources to host. Frequently, a figure of 10 USD per month is used as a general rule of thumb, but if you're willing to utilize budget VPS providers(I personally use racknerd and have been pleased with them) you can come in significantly under this figure. At the time of this writing (even in February), you can get a 4GB KVM VPS for under $44 USD per year(or $3.67/month) under the 'Black Friday 2021' category of the store page- this is the size I used for my own instance, and it has yet to exceed utilizing 1/3 of that. For my 2 user instance, I could probably have gotten away with the 2.5GB VKM VPS (27.88/year), but I wanted to account for potential spikes in utilization (as I wasn't familiar with the application's behavior) and additional active users.

In order to measure resource utilization, I decided to utilize the New Relic Infrastructure agent– I chose them simply because their service is free, and the agent is open source. It’s critical to monitor resource utilization for your instance to keep things running smoothly for you and your users, and to identify anomalous patterns before they create problems.

After several weeks of active use, I found that a basic Pleroma (Soapbox-be specifically) installation required little disk space:

root@:~# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        24G  7.0G   16G  31% /

With this small volume, my disk utilization increased 6.5% in 2 weeks. The increase was totally linear over that timeframe. I’ll need to increase the size of my root volume in the near-ish future if this increase remains linear.

The Memory utilization was also extremely linear, ranging from 1.29G to 1.32G at peak.

CPU utilization did not exceed 10% over the same 2 weeks, and disk IOPS and network traffic have remained trivial.

The barrier to entry for hosting a Pleroma instance is pretty trivial. It requires few resources, sub zero tech knowledge, and is cheap.

The Fediverse is a very unique place with a vast array of software offerings.

My present favorite option for Fediverse software is Soapbox. It adds features at a relatively rapid pace, utilizing an actively developed fork of Pleroma on the backend to facilitate the addition of features such as quote posts that aren't typically available on other fediverse platforms.

The instructions for installation of Soapbox are available here: https://soapbox.pub/install/

If you would like access to the newest features and improvements made to Soapbox, you can switch to the develop branch (some features of the develop branch frontend require the develop branch backend to function, so both need to be upgraded). While the develop branch is still in the testing phase and provided 'as-is' without any warranty, many of the largest and most active Fediverse Soapbox instances utilize it without issue(and I personally strongly prefer it from a performance standpoint).

You can upgrade to the develop branch by running the following commands on an existing built-from-source Soapbox install that uses asdf to manage Erlang/Elixir versions(you can switch the the asdf managed package from repo packages via the instructions here):

cd /opt/pleroma
sudo -Hu pleroma bash
git remote set-url origin https://gitlab.com/soapbox-pub/soapbox.git
git fetch origin --tags
git checkout develop

It is likely that you will need to update your toolchain via running asdf install as the Pleroma user. This may install new plugin versions- in such a case, you'll see a message similar to the below:

Erlang 24.1.6 has been installed. Activate globally with:

    asdf global erlang 24.1.6

Activate locally in the current folder with:

    asdf local erlang 24.1.6

Make sure to activate the new plugin versions prior to running additional commands. The following commands will recompile Soapbox BE:

mix local.hex --force
mix local.rebar --force
mix deps.get
MIX_ENV=prod mix compile

If you started with a Pleroma version prior to 2.3, the database will also require migrating:

MIX_ENV=prod mix ecto.migrate

It's not likely that the systemd service file will change frequently, but it's best practice to copy over the most recent version pulled via:

cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.service
systemctl enable --now pleroma.service

Once the Pleroma service restarts, you can then install(the process is also identical for updating) the develop branch for the frontend, which is pretty trivial to accomplish. First, run:

curl -L https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/develop/download?job=build-production -o soapbox-fe.zip Then, unzip the new front end into place via:

busybox unzip soapbox-fe.zip -o -d /opt/pleroma/instance

At this stage, you should be able to view your timeline successfully and enjoy the improvements of the develop branch.

Learning how to make Javascript perform tasks asynchronously is important for dealing with many routine tasks when writing applications.

Consider the following function code that performs a dns lookup, which doesn't behave as expected when called:

 const urlResolves = passedUrl => {
  let result = 'default';
  dns.lookup(passedUrl, (err, address, family) => {
    if(err){ 
      console.log('error in resolution');
      console.log(err);
      result = false;
      } else {
        console.log(`address is ${address}, family is IPv${family}`);
        result = true;
      }
  });
  console.log(`the result is: ${result}`);
  return result;
}

When I pass a valid hostname to this function, the result returned will almost always be default. However, the message from the successful block will also print the address and family of the passed URL.

This occurs because the function returns before the DNS resolution has completed(and the value of the result variable is modified). In order to sort this behavior, there are a few primary approaches. In this first short post, I'll briefly discuss Callbacks and demonstrate how to perform a dns lookup using them.

This method for dealing with async calls one method on completion of a function, effectively chaining them.

Here is a version of the urlResolves function that takes a callback:

 const urlResolves = (passedUrl, callback) => {
  let result = 'default';
    dns.lookup(passedUrl, (err, address, family) => {
    if(err){ 
      console.log(`error in resolution of ${passedUrl}`);
      result = false;
      callback(err, result);
      } else {
        console.log(`address is ${address}, family is IPv${family}`);
      result = true;
      callback(null, result);
      }
    });
};

You can then call this function and pass an anonymous function as the callback, returning any error as the first argument to the current function's callback (per node convention).

urlResolves(providedUrl, (err, bool) => {
    if(err){
       res.json({error: 'invalid url'});
       return;
    } else {
      console.log('returning json from dns lookup');
      res.json({original_url: req.body.url, short_url: 'placeholder'});
      return;
    }
  });

While useful for solving trivial problems, using callbacks doesn't scale effectively and creates headaches, particularly as you may find that you have to nest them(this is called 'the pyramid of doom').

A 'Promise' is a better option for accomplishing the same thing. A Promise object either results in a success(which is handled by the Javascript callback resolve) or a failure (which is handled by the reject callback). Whatever is passed to the resolve callback will be passed as a parameter in functions chained via then. Here's a function that performs DNS lookups using this approach- it returns a promise, and the address is then passed to the function chained with then.

const doesResolve = url => {
        return new Promise((resolve, reject)=> {
                dns.lookup(url, (err, address, family) => {
                if(err){
                        console.log(err);
                        reject(err);
                } else {
                console.log('lookup successful');
                resolve(address);
                }
                });
        });
}

I wrote a simple wrapper function for console.log to demonstrate the order in which the functions are executed, and then called it following doesResolve:

const logThis = thingToLog => {
        console.log('Initiating logThis function');
        console.log(thingToLog);
}


doesResolve('www.google.com')
        .then(result => logThis(result));

You can chain additional functions with .then, each of which will pass the value returned as a parameter to the next function. For example:

doesResolve('www.google.com')
        .then(result => result + " first function")
        .then(result => result + " second function")
        .then(result => logThis(result)); //logs "142.251.33.68 first function second function"

You can also return an additional Promise if the additional handlers need to wait.

Later, I'll be editing this post with the addition of Async/Await from ES7.

After coming back to some exercises on exercism involving closures, I quickly learned that my fundamental understanding of them was not what I'd believed. Whether this is due to a month passing since I visited the subject or the concussion I sustained in a recent car accident, I don't know.

In any case, I'm going to write (with likely overlap from previous entries) some very fundamental Closures examples, in the hopes to both document this and reference it later.

Consider the following code:

let runningTotal = 0;
const addToTotal = (num1) => {runningTotal += num1};

const funcBuilder = (unaryFunc) => {
     return (arg) => {unaryFunc(arg)}
    }

const addThem = funcBuilder(addToTotal);

addThem(6); //6
addThem(2); //8

In this simple example, a given number is added to runningTotal via an anonymous function returned by funcBuilder(). This doesn't make use of closures. If you have multiple totals to track, this approach isn't effective, as each successive call will still use the original runningTotal.

const addThem2 = funcBuilder(addToTotal);
addThem2(4); //12, added to existing runningTotal

You can seen an example pulled from the javascript.info tutorial on closure/Variable scope that illustrates use of closure to instantiate/track separate running totals:

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

counter and counter2 will increment independent instances of the count variable. The count variables in the above example will be tracked by Javascript, and not cleaned up until all instances of the returned anonymous functions no longer reference this variable.

In my time helping people troubleshoot Sentinel, a very large portion of issues can be traced back to a single concept:

computed or known after apply values.

This is a simple concept in theory- say that you wanted to perform a Sentinel policy check against a newly created AWS instance via it's instance ID- this wouldn't be possible, as Sentinel takes place between the Terraform plan and apply phases. There's no way of knowing what a given instance ID is going to be until after the AWS API call that Terraform makes is complete(after the apply).

This behavior is pretty straightforward- if you can't see the value before the apply, you can't easily use Sentinel(at least tfplan) to check them.

Whether a value is computed depends on how the resource in the associated provider is written.

However, it can be complex at times, as you may be operating on a static item (such as IAM policy) that may use computed values within. If any portion of an given item is computed, Sentinel treats the entire thing as computed.

As such, it's best to break things up into individual items, which reduces the chance that a given piece of that item is computed. For IAM policies, you can use the aws_iam_policy_document data source.

Because of a quirk of the behavior of terraform show -json(which generates the data that Sentinel policies run against), data sources without computed values appear in tfstate/v2 and not tfplan/v2– you'll need the associated import to effectively operate on them.

In Sentinel mocks, you can check for this by reviewing after_unknown, which contains a boolean indicating whether a given value is computed/known after apply.

The data source present in the mocks also contains this field, which will tell you the intended behavior of the value.

There are only a couple of realistic ways to deal with these values in Sentinel- either use tfconfig to operate against the Terraform configuration itself (this is fragile, error-prone, and might not even work), or use tfstate to check AFTER the run is completed to flag violating resources for developers to fix later.

It's fairly straightforward to write your own custom error types in Javascript. The most basic syntax looks something like this:

class ArgumentError extends Error {
  constructor() {
    super(`The sensor is broken. Null temperature value detected.`);
  }
}

It's important to first understand the workings of the actual Error prototype, along with Javascript prototypal inheritance.

A Javascript class is a template for objects- it will always have a constructor method, which is automatically executed when a new instance of this class is created. If you don't include one, Javascript will include one for you.

The extends statement allows your object to access all of the methods and properties of the parent method.

In this context, it means that ArgumentError has access to all of the contents of Error. However, to actually access those contents you need to use the super function. The super function allows you to call the parent class constructor with the arguments provided, or call functions on the object's parent. In this context, the message about the sensor being broken is passed into super, which calls the parent Error constructor passing the message as an argument. The Error constructor takes a few optional parameters- providing the string will pass this as the message.

Effectively, this means that ArgumentError is simply an Error that is passed a specific message.

You can also pass a specific argument in a trivially more complex example:

export class OverheatingError extends Error {
  constructor(temperature) {
    super(`The temperature is ${temperature} ! Overheating !`);
    this.temperature = temperature;
  }
}

You can then perform checks against the property of the error, calling functions accordingly.

try {
    actions.check();
  } catch (error) {
    if(error instanceof ArgumentError) {
      actions.alertDeadSensor();
    } else if(error instanceof OverheatingError && error.temperature > 650) {
      actions.shutdown();
}

Writing functions that are nested in a functional style in JavaScript can be tricky. For instance, consider the following code:

const composeu = (unaryFunc1, unaryFunc2) => {
    return function(arg) {
        return unaryFunc2(unaryFunc1(arg));             
    };
};

In order for this to work properly, the nested function invocations need to be written inside out. Existing functions can be effectively strung together/piped in a UNIX like fashion. The spread operator (...) allows for the number of functions chained to be variable.

In the following similar example, a function calling two binary functions are called on a set of arguments (known length) is returned:

const composeb = (binFunc1, binFunc2) => {
    return function(arg1, arg2, arg3) {
        return binFunc2(binFunc1(arg1,arg2), arg3);
    }
}

You can also use these variables to control function flow, such as by storing a local variable. I wasn't able to figure out the following problem initially:

// Write a `limit` function that allows a binary function to be called a limited number of times

const limit = (binFunc, count) => {
    return function (a, b) {
        if (count >= 1) {
            count -= 1;
            return binFunc(a, b);
        }
        return undefined;
    };
}

In my line of work, I frequently end up helping customers who are running into issues with implementing Hashicorp Sentinel policies.

It's a “policy as code” product that ties in nicely with the Infrastructure as Code nature of Terraform. For additional information around the philosophical approach behind Sentinel and the advantages it confers, I recommend seeing this post from one of Hashicorp's founders, Armon Dadgar:

https://www.hashicorp.com/resources/introduction-sentinel-compliance-policy-as-code

Sentinel is being revised very rapidly and is a paid product, so finding code examples that both actually work and are current can be very tricky. One of the best places to start is this repository of example Sentinel policies(and helper functions) for various cloud providers:

https://github.com/hashicorp/terraform-guides/tree/master/governance/third-generation

Though Hashicorp literature states “Sentinel is meant to be a very high-level easy to learn programming language”, it isn't easy, particularly if you aren't familiar with the general syntax of go. The difficulty extends outside the realm of the syntax to the actual way that troubleshooting is implemented, and the lack of IDE tooling (outside of a VSCode syntax highlighter). Debugging is chiefly a matter of using print and then running the sentinel binary with the trace flag, as error messages are often quite opaque.

For example, say you're creating a policy that is meant to check for tags, and you unexpectedly run into a situation where undefined is being returned where it's not being expected. This is typically the result of unexpected provider configuration, such as the addition of aliases.

Analyzing this can require a mixture of tfplan, tfconfig, and even tfstate if data sources therein don't contain computed values. Understanding computed values is critical to effectively writing Sentinel code- a lot of resources have values that aren't known until after an apply is performed. Because Sentinel runs occur between the plan and apply phases, it's not possible for a policy to effectively operate against such values. If your Sentinel mocks contain unknown for 'after' the value is likely computed.

If you're using the helper functions from the linked Hashicorp repository, these will often require some combination of all three imports.

At present, the only way to iterate over provider aliases is to use tfconfig.providers, which returns a JSON object containing specified providers.

Enter your email to subscribe to updates.