ConfigU: A SaaS offering for SCM

Published: Apr 4, 2023 by Isaac Johnson

Ran Cohen, CTO/Co-Founder of ConfigU asked me to take a look at their offering; ConfigU, “Configuration Management as a Service”. I’m always game to check out new DevOps, SCM and SRE tools and if there is a Community/Free tier, I’m even more excited to dig in and see it can play a part in my work streams.

Today, we’ll setup ConfigU and follow a basic “Getting Started” flow. We’ll expand it to try new “schemas” and play with their built-in webhooks. We’ll integrate it into a CICD Flow using Azure DevOps Pipelines and Token based auth. Lastly, we’ll look at the pros/cons and compare with what we might achieve in source (GIT) and using AKV.

Company background

ConfigU is a Tel Aviv based started founded in late 2020 or early 2021 (the oldest archive I see is from Sept 2021). It was started by Peleg Porat and Ran Cohen, Peleg starting it after being a Software Developer at F5 Networks. Ran then joined him having been a Full Stack Engineer at Tricentis Testim. ConfigU is a private company that’s raised at least $2.8m in seed funding from private investors and Cardumen Capital.

I saw official “we’re live” announcements on Peleg’s LI as recently as five months ago and he demoed at EISP’s 2021 Demo day in Oct of 2021.

I tried to get an idea where Ran and Peleg overlapped and best I can tell, the only past shared experience was in the IDF where they overlapped in Military Intelligence between 2014 and 2016.

Getting Started

We can click “Get Started” and signup with an IdP like Github, Google or Microsoft

/content/images/2023/04/configu-01.png

The signup wizard lets us set a name and theme (I went with light)

/content/images/2023/04/configu-02.png

I’ll set a few more details

/content/images/2023/04/configu-03.png

then get dropped into a “Getting Started” page

/content/images/2023/04/configu-04.png

The next steps come from following that guide:

$ curl https://cli.configu.com/install.sh | sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2420  100  2420    0     0  10852      0 --:--:-- --:--:-- --:--:-- 10852
This script requires superuser access.
You will be prompted for your password by sudo.
[sudo] password for builder:
Installing CLI from https://cli.configu.com/channels/stable/configu-linux-x64.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 79.8M  100 79.8M    0     0  30.3M      0  0:00:02  0:00:02 --:--:-- 30.3M
v18.15.0
configu installed to /usr/local/bin/configu
@configu/cli/0.8.0 wsl-x64 node-v18.15.0

I’ll init my workspace


✔ /home/builder/Workspaces/jekyll-blog/get-started.cfgu.json generated with 3 records

The login is actually pretty slick. It lets me use a token, or if set to user, pops up a browser window I can use to login right then

$ configu store upsert --type 'configu'
? select authentication type User
press any key to open up the browser to login or press ctrl-c to abort.
you should see the following code: 629 170 429. it expires in 15 minutes.:
? select organization Freshbrewed
ℹ configu upserted to cli configuration

/content/images/2023/04/configu-05.png

I’ll set the config

$ configu upsert \
> --store 'configu' --set 'example' --schema './get-started.cfgu.json' \
> --config 'GREETING=hey' --config 'SUBJECT=Isaac Johnson'
ℹ configs upserted successfully

Then I can pull it back down to save in a local JSON

$ configu export \
--from 'store=configu;set=example;schema=./get-started.cfgu.json' \
--format 'JSON' \
> 'greeting.json'

$ cat greeting.json
{
  "MESSAGE": "hey, Isaac Johnson!",
  "SUBJECT": "Isaac Johnson",
  "GREETING": "hey"
}

In the WebUI, I can see settings I added

/content/images/2023/04/configu-06.png

Installations

We did Linux without trouble. We can see there are plenty of other options

/content/images/2023/04/configu-08.png

Let’s try Windows next

I created a quick directory in which to test

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows

PS C:\Users\isaac> mkdir ConfigUTest


    Directory: C:\Users\isaac


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          4/2/2023  11:38 AM                ConfigUTest


PS C:\Users\isaac> cd .\ConfigUTest\
PS C:\Users\isaac\ConfigUTest>

It was a pretty slow install and left the admin PS window open. It didn’t seem to work in the source window - likely because this tool installs NodeJS 18 and then the app

/content/images/2023/04/configu-09.png

I could get it working, however, in a fresh prompt

C:\Users\isaac\ConfigUTest>"C:\Program Files\configu\bin\configu" init --get-started
√ C:\Users\isaac\ConfigUTest\get-started.cfgu.json generated with 3 records

C:\Users\isaac\ConfigUTest>"C:\Program Files\configu\bin\configu" store upsert --type "configu"
? select authentication type User
press any key to open up the browser to login or press ctrl-c to abort.
you should see the following code: 422 306 180. it expires in 15 minutes.:
? select organization Freshbrewed
i configu upserted to cli configuration

Where I could export as well

C:\Users\isaac\ConfigUTest>"C:\Program Files\configu\bin\configu" export --from "store=configu;set=example;schema=./get-started.cfgu.json" --format "JSON" > greetings.json

C:\Users\isaac\ConfigUTest>type greetings.json
{
  "MESSAGE": "hey, Isaac Johnson!",
  "SUBJECT": "Isaac Johnson",
  "GREETING": "hey"
}

CICD

Here I was plesantly suprised how many CICD tooks they supported

/content/images/2023/04/configu-10.png

Let’s try AzDO to start.

I’ll create a new Azure Pipeline

/content/images/2023/04/configu-11.png

I’ll choose Azure Repos and pick a Repo to use. Then I’ll ask for a starter pipeline

/content/images/2023/04/configu-12.png

At this point, you should see something similar

/content/images/2023/04/configu-13.png

I’ll setup the pipeline to just download the config and show it

trigger:
- master

pool:
  vmImage: ubuntu-latest

steps:
- script: |
    (curl https://cli.configu.com/install.sh || wget -qO- https://cli.configu.com/install.sh) | sudo sh
  displayName: Install Configu CLI
- script: |
      echo "Configu Export"
      export --from 'store=configu;set=example;schema=./get-started.cfgu.json' \
      --format 'JSON' > 'greeting.json'
  displayName: "Export ConfigU"
  env:
    CONFIGU_ORG: $(CONFIGU_ORG)
    CONFIGU_TOKEN: $(CONFIGU_TOKEN)

- script: |
    echo "Now show the config"
    cat ./greeting.json
  displayName: 'Show the Configu Config'

The two variables we need are CONFIGU_ORG and CONFIGU_TOKEN.

For the time being, we can just set that in the “Variable” section

/content/images/2023/04/configu-14.png

Click “New Variable” and we can define the org (for me, that is “freshbrewed”)

/content/images/2023/04/configu-15.png

Back in ConfigU, I’ll want to create a Token. I can do that in Settings/Tokens:

/content/images/2023/04/configu-16.png

I’ll name it

/content/images/2023/04/configu-17.png

Then it will show it for me to copy the one time

/content/images/2023/04/configu-18.png

Back in Azure Pipelines, I’ll make that New Variable also a secret, but one that could also be overridden

/content/images/2023/04/configu-19.png

We can now see both are listed

/content/images/2023/04/configu-20.png

Lastly, I’ll save and run

/content/images/2023/04/configu-21.png

I’ll use a new branch, and in my case, not start a PR

/content/images/2023/04/configu-22.png

This kicks off a build

/content/images/2023/04/configu-23.png

It didn’t like the export option

/content/images/2023/04/configu-24.png

Maybe you caught the issue already - but I neglected to set the binary in the export command. I also missed a few other steps like init.

I fixed that, set a local store, and then updated trigger branch

trigger:
- azure-pipelines-configu

pool:
  vmImage: ubuntu-latest

steps:
- script: |
    (curl https://cli.configu.com/install.sh || wget -qO- https://cli.configu.com/install.sh) | sudo sh
  displayName: "ConfigU: Install"

- script: |
      echo "Configu Init"
      configu init --get-started
  displayName: "ConfigU: Init"

- script: |
      set +x
      echo "Configu Store Set"
      configu store upsert --label "my-store" --connection-string "store=configu;org=$(CONFIGU_ORG);token=$(CONFIGU_TOKEN)"

      echo "Configu Store Use"
      configu export --from "store=my-store;set=example;schema=./get-started.cfgu.json" \
      --format 'JSON' > 'greeting.json'
  displayName: "ConfigU: Store Set and Export"

- script: |
    echo "Now show the config"
    cat ./greeting.json
  displayName: 'Show the Configu Config'

We can now see it works just dandy

/content/images/2023/04/configu-25.png

Webhooks

I saw an “Integrations/Webhooks” area, but little documentation around usage.

I Created a new webhook

/content/images/2023/04/configu-26.png

I used webhook.site to see what the payload would be

I ran a test

/content/images/2023/04/configu-27.png

and saw this was sent

/content/images/2023/04/configu-28.png

{
  "webhook": {
    "_id": "rrv6pajkN3acESPJW94hXxBdjN14h5dG",
    "contentType": "application/json",
    "createdAt": 1680458749836,
    "events": [],
    "isActive": true,
    "name": "InvokePipeline",
    "orgId": "freshbrewed",
    "updatedAt": 1680458749836,
    "url": "https://webhook.site/697160e1-c271-4f39-886a-2271b4b69a3a"
  },
  "entityId": "rrv6pajkN3acESPJW94hXxBdjN14h5dG",
  "text": "Webhook 'InvokePipeline' has been invoked"
}

I saw that the webhook fires on configu exports, such as when I ran the pipeline

/content/images/2023/04/configu-29.png

And an update (upsert) looks like

{
  "webhook": {
    "_id": "rrv6pajkN3acESPJW94hXxBdjN14h5dG",
    "contentType": "application/json",
    "createdAt": 1680458749836,
    "events": [],
    "isActive": true,
    "name": "InvokePipeline",
    "orgId": "freshbrewed",
    "updatedAt": 1680458749836,
    "url": "https://webhook.site/697160e1-c271-4f39-886a-2271b4b69a3a"
  },
  "entityId": "UYyt2MFfuDtxJst5pCpD0IpbUsWmRnKn",
  "text": "Configs have been upserted to the following sets: example"
}

Schema

We can look at the ConfigU types to see some options

Type Also Requires Example  
String None “Hello, World!”  
Boolean None true  
Number None 123456  
RegEx pattern foo bar
UUID None Any generated UUID  
Email None foo@example.com, foo+bar@example.com  
MobilePhone None +1 000 000 0000  
Locale None EN-GB  
LatLong None 41 24.2028, 2 10.4418  
SemVer None 1.2.3-4  
Color None #ffffff  
IPv4 None 127.0.0.1  
IPv6 None 2600:4040:b5c4:1100:216:3eff:feda:953b  
Base64 None A Base64 Encoded Value  
Hex None A Hex Encoded Value  
MD5 None A MD5 Hashed Value  
SHA None A SHA Hashed Value  
Country None GB GR  
AWSRegion None eu-west-1 us-west-2  
AzureRegion None brazilsouth australiaeast  
GCPRegion None asia-south1 asia-southeast2  
Currency None AUD USD  

The Default “Hello World” Schema that was created at init looks like

$ cat get-started.cfgu.json
{
  "GREETING": {
    "type": "RegEx",
    "pattern": "^(hello|hey|welcome|salute|bonjour)$",
    "default": "hello"
  },
  "SUBJECT": {
    "type": "String",
    "default": "world"
  },
  "MESSAGE": {
    "type": "String",
    "template": ", !",
    "description": "Generates a full greeting message"
  }
}

We can see that take effect if we try and push up a value outside the schema:

$ configu upsert --store 'configu' --set 'example' --schema './get-started.cfgu.json' --config 'GREETING=wassup' --config 'SUBJECT=Isaac Johnson'
    Error: invalid config value "wassup" for key "GREETING" at UpsertCommand > run, value "wassup" must be of type "RegEx"

Let’s create a different config and use it to see an example

Here I’ll create a JSON around football:

$ cat NFL.cfgu.json
{
  "BESTTEAM": {
    "type": "RegEx",
    "pattern": "^(vikings|packers)$",
    "default": "vikings"
  },
  "FANLOYALTY": {
    "type": "Number",
    "default": "1"
  },
  "COUNTRY": {
    "type": "Country",
    "default": "US",
    "description": "Country of Origin"
  }
}

Now if we tried to set a config with say, the Chargers

$ configu upsert --store 'configu' --set 'realfootball' --schema './NFL.cfgu.json' --config 'BESTTEAM=chargers'
    Error: invalid config value "chargers" for key "BESTTEAM" at UpsertCommand > run, value "chargers" must be of type "RegEx"

It would fail, both because the Regular Expression failed the test and the Chargers are dead to me after they left San Diego.

But if we set it to my Vikings

$ configu upsert --store 'configu' --set 'realfootball' --schema './NFL.cfgu.json' --config 'BESTTEAM=vikings'
ℹ configs upserted successfully

We can see it works.

We can also see that it’s been added in the ConfigU UI

/content/images/2023/04/configu-30.png

If I wish to edit the cfgu JSON, I can use the editor in the Web UI

/content/images/2023/04/configu-31.png

Which I could use to test the schema validation

/content/images/2023/04/configu-32.png

We can see a few small limitations here.

For one, if I update a value, there is no history stored on the configs, at least as exposed in the UI

$ configu upsert --store 'configu' --set 'realfootball' --schema './NFL.cfgu.json' --config 'BESTTEAM=packers'
ℹ configs upserted successfully

/content/images/2023/04/configu-33.png

I can really only see the field was updated by me at a time

/content/images/2023/04/configu-34.png

with a resulting webhook

{
  "webhook": {
    "_id": "rrv6pajkN3acESPJW94hXxBdjN14h5dG",
    "contentType": "application/json",
    "createdAt": 1680458749836,
    "events": [],
    "isActive": true,
    "name": "InvokePipeline",
    "orgId": "freshbrewed",
    "updatedAt": 1680458749836,
    "url": "https://webhook.site/697160e1-c271-4f39-886a-2271b4b69a3a"
  },
  "entityId": "6SLUuVSxzKKmcjtomegH14s6Cq9K23wE",
  "text": "Configs have been upserted to the following sets: realfootball"
}

ConfigU also would not be a good place to put secrets.

If I were to add a password field, for instance

$ cat NFL.cfgu.json
{
  "BESTTEAM": {
    "type": "RegEx",
    "pattern": "^(vikings|packers)$",
    "default": "vikings"
  },
  "FANLOYALTY": {
    "type": "Number",
    "default": "1"
  },
  "COUNTRY": {
    "type": "Country",
    "default": "US",
    "description": "Country of Origin"
  },
  "PASSWORD": {
    "type": "String"
  }
}

And then use it

$ configu upsert --store 'configu' --set 'realfootball' --schema './NFL.cfgu.json' --config 'PASSWORD=deflategate'
ℹ configs upserted successfully

It would be plain text in the UI

/content/images/2023/04/configu-35.png

This isn’t a deal breaker, however.

We could make a reference to a Key Vault, for instance, and fetch it in a second pass with a pipeline;

$ configu upsert --store 'configu' --set 'realfootball' --schema './NFL.cfgu.json' --config 'PASSWORD=akv:mysecret'
ℹ configs upserted successfully

Then fetch it

$ configu export --from "store=my-store;set=realfootball;schema=./NFL.cfgu.json" --format "JSON"
{
  "PASSWORD": "akv:mysecret",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "packers"
}

So let’s say I want to use a secret in AKV

/content/images/2023/04/configu-36.png

I could make a reference to it in the config

$ configu upsert --store 'configu' --set 'realfootball' --schema './NFL.cfgu.json' --config 'PASSWORD=akv:idjtestsecret'
ℹ configs upserted successfully

Then in use, I could export the config

$ configu export --from "store=my-store;set=realfootball;schema=./NFL.cfgu.json" --format "JSON" > t.json
$ cat t.json
{
  "PASSWORD": "akv:idjtestsecret",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "packers"
}

Then use something like a perl script

$ cat t.pl
#!/usr/bin/perl
#

my $filen = $ARGV[0];

open(FILEH,$filen);
@filec = <FILEH>;
close(FILEH);

foreach my $line (@filec)
{
   if ($line =~ /^(.*)"akv:([^"]*)"/)
   {
           my $prefix=$1;
           my $newValue=`az keyvault secret show --name $2 --vault-name idjakv -o json | jq -r .value | tr -d '\n'`;

           if ($line =~ /,$/)
           {
               print "$prefix\"$newValue\",\n";
           } else {
               print "$prefix\"$newValue\"\n";
           }
   }
   else
   {
           print $line;
   }
}

Now I could use it to fetch that on the fly in the config

$ perl ./t.pl t.json
{
  "PASSWORD": "this is a test 2",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "packers"
}

Plans

I can save up to 50 “configs” (I might call them just settings) and have up to 5 users in the free community edition.

I think that’s a pretty reasonable start to kick the tires

/content/images/2023/04/configu-07.png

To upgrade, it is a conversation. There is no specified pricing anywhere on the site.

Reaching out to ConfigU, I was given the range of a “a few dozen to a few hundred dollars per month”, depending on usage. The Enterprise plan would then extend beyond that.

Alternate approaches

This presents a bit of a quandary on config storage.

EVEN if I desired to separate config, why would I externalize it?

That is, if my config is arguably non-secret data (or references to secret data), then why not just check it in to source?

$ cat dev.json
{
  "PASSWORD": "akv:dev-idjtestsecret",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "packers"
}
$ cat qa.json
{
  "PASSWORD": "akv:qa-idjtestsecret",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "vikings"
}
$ cat prod.json
{
  "PASSWORD": "akv:prod-idjtestsecret",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "packers"
}

Alternatively, I could have “settings.json”, but on different branches in source (with different values). This would allow merging between and branch rules on update;

builder@DESKTOP-72D2D9T:~/Workspaces/tfAnsibleAzure$ cat settings.json
{
  "PASSWORD": "akv:idjtestsecret",
  "COUNTRY": "US",
  "FANLOYALTY": "1",
  "BESTTEAM": "packers"
}
builder@DESKTOP-72D2D9T:~/Workspaces/tfAnsibleAzure$ git checkout qa-main
Switched to branch 'qa-main'
builder@DESKTOP-72D2D9T:~/Workspaces/tfAnsibleAzure$ cat settings.json
{
  "PASSWORD": "akv:qa-idjtestsecret",
  "COUNTRY": "US",
  "FANLOYALTY": "2",
  "BESTTEAM": "packers"
}

We could store all our config and secrets in AKV and prefix them by environment to keep them seperate


- task: AzureKeyVault@2
  inputs:
    azureSubscription: 'Pay-As-You-Go'
    KeyVaultName: 'idjakv'
    SecretsFilter: 'qa-*'
    RunAsPreJob: false

Lastly, in a similar fashion to source, we could always make an AKV per environment as our Access Policies are at the Key Vault level and bring them in at each stage


- task: AzureKeyVault@2
  inputs:
    azureSubscription: 'Pay-As-You-Go'
    KeyVaultName: 'settings-qa'
    SecretsFilter: '*'
    RunAsPreJob: false

Which could substitute values in a file with the FileTransformer

# Update appsettings.json via FileTransform task.
- task: FileTransform@1
  displayName: 'File transformation: settings.json'
  inputs:
    folderPath: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
    targetFiles: '**/settings.json'
    fileType: json

Or the often used replace tokens task

- task: qetza.replacetokens.replacetokens-task.replacetokens@3
  displayName: 'Replace tokens'
  inputs:
    targetFiles: |
      **/*.config
      **/*.json => outputs/*.json

I know this is an Azure specific implementation, but the end results are that my secrets are entirely under my control in my own subscription. I don’t have to worry about access controls on a new vendor. I also have variable history - I know what changed, when, and by whom

/content/images/2023/04/configu-37.png

I can also set metadata tags, descriptions (notes) and expiration dates, if that affects our data per-secret

/content/images/2023/04/configu-38.png

Summary

I’m always cautious on new tooling that offers SaaS only and no obvious way to dump all my data. It’s not that vendor lock-in is always bad, but I like to know my options. Arguably you can view your full Configs on a page so one can always screen-scrape.

I also have a general bent against companies with opaque pricing. However, in this case, the offering is new enough they likely are working out what the specifics on prices ought to be.

As far as new products go, the documentation is great. They cover multiple deployment types, tools and languages with pretty easy to follow guides

e.g. Looking at Schema and Keys:

/content/images/2023/04/configu-39.png

Additionally, not every company can easily use cloud services so having something not married to Azure, AWS or GCP might be valuable in specific situations.

Overall, I found ConfigU to be an interesting offering with a fairly fast free tier and worthy of consideration.

ConfigU SCM Azure Azure DevOps

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes