Secrets Management with Teller

29.06.2022Stefan Welsch
Tech teller Cloud native Hands-on

Today I want to give you a small but very useful tool. Teller is a “secrets manager” for developers when it comes to building cloud-native apps. The project was launched in March 2021 and is written entirely in Go.

The principle behind it is simple. Never use the terminal to input or output passwords in plain text. Let’s take a quick look at a small example and find out why this is not a good practice.

Let’s assume you use a secrets manager in your company, such as AWS Secrets Manager, to protect your secrets. Passwords or tokens are kept “safe” and you can be sure that only those who have access to the corresponding entity in the Secrets Manager can see the secret. So far so good.

Now there is a bug on a system when querying a secured rest endpoint and you would like to look locally at the data coming back from this endpoint. In our case, the endpoint is secured with a simple “Basic Auth” (of course, you should choose a more secure method here, but this is enough for us for an illustrative example). So we open our console and do a curl against the endpoint. Before we do that, we copy the corresponding password from our Secrets Manager to the clipboard.

1
$ curl -H "Accept: application/json" -u "admin:12345" https://my-secure-rest-endpoint.com/api/data/get

In response we get the desired result in JSON format.

1
2
3
4
5
6
7
8
{
    "success": { "true",
  	"data": {
      "``name'': ``test'',
      "title": "This is a title",
      "text": "This is a text"
    }
}

We realise that there is a typo in “title” and we can fix it very quickly. Now we can treat ourselves to a coffee. Unfortunately, you forgot to lock the PC, but luckily the console window was closed so that nobody can see the password. Now, however, a Linux expert comes along and knows the magic command with which he can still read out the password. So he sits down at your PC, opens the terminal and types the command history. He can then display all the commands that were executed in your console. The last entry he sees is the curl command and thus also the user and password in plain text.

Long story short. Typing passwords in plain text on the terminal or storing them in ENV variables is not really a good idea. This is exactly where Teller helps us. Teller also offers us a lot of possibilities to manage or exchange secrets in the different secret managers.

Let’s take a look at an example. First, we have to install Teller locally. There is a binary for macOS, Linux and Windows. I use macOS and therefore enter the following command.

1
$ brew tap spectralops/tap && brew install teller

Then we create a new project. To do this, we type teller new and then we have to configure the name and the providers. The name here is my-project and as provider we take only AWS Secrets Manager for now.

1
2
3
4
5
$ plate new
? Project name ? my-project
? Select your secret providers AWS Secrets Manager
? Would you like extra confirmation before accessing secrets? No
Created file: .teller.yml

As we can see, a file called .teller.yml has now been created in our user folder. Let’s have a look at this file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ bat .teller.yml
12 │ project: my-project
34# Set this if you want to carry over parent process' environment variables
5# carry_env: true
678#
9# Variables
10#
11# Feel free to add options here to be used as a variable throughout
12# paths.
13#
14 │ opts:
15 │ region: env:AWS_REGION # you can get env variables with the 'env:' prefix
16 │ stage: development
171819#
20# Providers
21#
22 │ providers:
23# configure only from environment
24 │ aws_secretsmanager:
25 │ env_sync:
26 │ path: prod/foo/bar
27 │ env:
28 │ FOO_BAR:
29 │ path: prod/foo/bar
30 │ field: SOME_KEY

As we can see, we get a configuration file, which we will now discuss in detail.

Line 14 - 16: Here we can define options which can be read later with opt. (e.g. region)

Line 22 - 30: Here we configure the provider.

In order for us to be able to test this, three things must already be in place: You must have a Secret in the AWS Secrets Manger, the AWS CLI must be installed and configured and you must have access to the corresponding Secret you want to read.

For my example, I created a secret with the ID dev/test/admin and two keys (user/pass). Accordingly, I can read it with the AWS CLI as follows:

1
2
3
4
5
6
7
8
9
$ aws secretsmanager get-secret-value --secret-id dev/test/admin

ARN: arn:aws:secretsmanager:eu-central-1:XXXXXXXXX:secret:dev/test/admin-3mhwYt
CreatedDate: '2022-05-13T08:26:58.096000+02:00'
Name: dev/test/admin
SecretString: '{"user":"admin","pass":"12345"}'
VersionId: 31dd694d-8c3c-4405-95a5-a25813028d48
VersionStages:
- AWSCURRENT

Those who have not yet installed the AWS CLI should interrupt here and do so. After the installation, you still have to run the command aws configure to get access via token.

Now we can get down to business. In the file .teller.yaml we now change the value of path: prod/foo/bar to path: dev/test/admin in the aws_secretsmanager provider at env_sync: and simply delete the section env. The file now looks like this:

1
2
3
4
5
6
...
22 │ providers:
23# configure only from environment
24 │ aws_secretsmanager:
25 │ env_sync:
26 │ path: dev/test/admin

So let’s test our changes. We enter the following command in the terminal: show.

1
2
3
4
5
$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] user = ad*****
[aws_secretsmanager dev/test/admin] pass = 12*****

As we can see, the secrets are read correctly and we can now use them with Teller. The secrets are masked on output so that they cannot be read in plain text.


ℹ️ You can control the masking of the output in the .teller.yml configuration file with the option redact_with.


Of course, we can also read only one key from the secret. To do this, we change the following in the .teller.yml and then read out the secret again with teller show.

1
2
3
4
5
6
7
22 │ providers:
23# configure only from environment
24 │ aws_secretsmanager:
25 │ env:
26 │ ADMIN_USER:
27 │ path: dev/test/admin
28 │ field: user
1
2
3
4
$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] ADMIN_USER = ad*****

Now we can see that only the corresponding secret is output.

Next, let’s look at how we can use these secrets in an application. To do this, we write a simple Hello World Go app in which we want to display the user. I create a new directory teller-golang and create a file main.go here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import (
"fmt"
"os"
)

func main() {
fmt.Printf("Hello, %s", os.Getenv("ADMIN_USER"))
}

When I run the program now, I get the following output:

1
2
~/teller-golang ❯ go run main.go
Hello, %

As we can see, the variable ADMIN_USER is not set. Let’s run the program again with Teller:

1
2
~/teller-golang ❯ teller run go run main.go
FATA[0000] could not read file error="open .teller.yml: no such file or directory" file=.teller.yml

We get an error because there is no Teller configuration file in our project directory. Because of the error we now see how handy Teller works at this point. For each project directory a separate configuration file can be created. This ensures that only the secrets that are really needed are used. This file can of course be checked into the VCS (Version Control System) so that all developers have access to the same Secrets.

So let’s copy the configuration file into our project directory and try the whole thing again.

1
2
3
4
~/teller-golang ❯ cp ~/.teller.yml ~/teller-golang/
~/teller-golang ❯ teller run go run main.go
-*- teller: loaded variables for my-project using .teller.yml -*-
Hello, admin%

As we can now see, the Teller variables are loaded and made available to the Go application in the form of environment variables.  🚀

Secrets Management with Teller

Teller offers us other useful tools, which not only simplify the development, but also the handling of secrets in general. For example, we can create new secrets directly in the provider. To do this, we extend the .teller.yml with the following entry

1
2
3
4
5
6
7
8
aws_secretsmanager:
env:
ADMIN_USER:
path: dev/test/admin
field: user
+ SECRET:
+ path: dev/test/admin
+ field: secret

Now we run the following command in the terminal.

1
$ teller put SECRET=my-new-secret --providers aws_secretsmanager -c .teller.yml

This command creates a new key in the specified path directly in AWS Secrets Manager.

image-20220513100834209

Teller also offers us the possibility to copy secrets between two or more providers and even to synchronise them.

To do this, of course, we first have to add another provider. So we open the Teller configuration file and add Google Secrets Manager as provider. If you don’t have a Google account yet, or if you haven’t installed the CLI, you can do so here. You will also need a service account. You can find the instructions for this here.

I create a new project teller-golang in the Google Cloud Console and create a secret MY_USER=bnova-stefan within this project.

1
2
3
4
google_secretmanager:
env:
MY_USER:
path: projects/teller-golang/secrets/MY_USER/versions/1

After we have inserted the provider, we can see if the secret can be read. If all configurations are correct, we should see the following output.

1
2
3
4
5
6
$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] SECRET = my*****
[google_secretmanager projects/tell...SER/versions/1] MY_USER = bn*****
[aws_secretsmanager dev/test/admin] ADMIN_USER = ad*****

Now we want to copy the secret from Google Secrets Manager to AWS Secret Manager. Unfortunately, synchronisation is not possible in Google Secret Manager.

To do this, we issue the following command in the terminal.

1
2
$ teller copy --from google_secretmanager --to aws_secretsmanager
Put MY_USER in aws_secretsmanager: no such key 'MY_USER' in mapping

We see an error message that the key MY_USER does not yet exist in the AWS Secret Manager. So before we can copy a Secret Value, the corresponding field must first exist in the target. So we extend our .teller.yaml with the following entry and then execute the command again.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
providers:
  aws_secretsmanager:
    env:
      ADMIN_USER:
        path: dev/test/admin
        field: user
      SECRET:
        path: dev/test/admin
        field: secret
    + MY_USER:
    + path: dev/test/admin
    + field: my_user
1
2
$ teller copy --from google_secretmanager --to aws_secretsmanager
Put MY_USER (dev/test/admin) in aws_secretsmanager: OK.

As we can see, the command was executed successfully. Let’s have a look at the result directly in the AWS Secret Manager.

image-20220516130615590

Et voilà! The new secret has been successfully copied. Also teller show shows us the new Secret in AWS of course.

1
2
3
4
5
6
7
$ teller show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] SECRET = my*****
[aws_secretsmanager dev/test/admin] MY_USER = bn*****
[google_secretmanager projects/tell...SER/versions/1] MY_USER = bn*****
[aws_secretsmanager dev/test/admin] ADMIN_USER = ad*****

Now we have seen how to create and copy a secret. Let’s take a look at how to delete a secret again. For this purpose Teller offers us the teller delete command. We don’t want to use the Admin User in our application anymore, but MY_USER. So we can delete ADMIN_USER. To do this, we execute the following command.

1
2
$ teller delete ADMIN_USER --providers aws_secretsmanager -c .teller.yml
Delete ADMIN_USER (dev/test/admin) in aws_secretsmanager: OK.

Deleted! We’ll look again directly in the AWS Secret Manager just to be sure.

image-20220516131434753

And here, too, the secret has been deleted. Let’s take a look at what teller show tells us.

1
2
3
4
5
6
7
plate show
-*- teller: loaded variables for my-project using .teller.yml -*-

[aws_secretsmanager dev/test/admin] SECRET = my*****
[aws_secretsmanager dev/test/admin] MY_USER = bn*****
[google_secretmanager projects/tell...SER/versions/1] MY_USER = bn*****
[aws_secretsmanager dev/test/admin missing] ADMIN_USER

We see in the last line that the status for the secret ADMIN_USER in the AWS Secret Manager is “missing”. This is correct and we can remove the entry from the teller.yaml.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
providers:
  aws_secretsmanager:
    env:
      ADMIN_USER: <-- delete
        path: dev/test/admin <-- delete
        field: user <-- delete
      SECRET:
        path: dev/test/admin
        field: secret
      MY_USER:
        path: dev/test/admin
        field: my_user

We have removed the secret completely.


ℹ️ Not all providers support all commands. For example, Google Secretmanager does not support teller delete.


Summary

Teller is a very practical tool if you want to share secrets in the developer team without having to display them in plain text. The practical configuration file teller.yaml makes it possible to store the same secrets in the project directly for all developers. The developers only need to have access rights to the secrets.

Secret management with Teller is very effective, but the big disadvantage is that not all providers support the same functionalities. For example, AWS Secret Manager offers synchronisation of the secrets, while Google does not support this. However, I only see a limited use case in daily business (e.g. migration project from AWS to Google).

Teller offers us another tool, teller redact, with which we can filter out passwords from a log file, for example. This is also very handy to make sure that you have not logged any passwords in the past without having to delete the log file.

We at b-nova will definitely consider Teller for future internal development projects. Stay tuned! 😄

Stefan Welsch

Stefan Welsch – Pionier, Stuntman, Mentor. Als Gründer von b-nova ist Stefan immer auf der Suche nach neuen und vielversprechenden Entwicklungsfeldern. Er ist durch und durch Pragmatiker und schreibt daher auch am liebsten Beiträge die sich möglichst nahe an 'real-world' Szenarien anlehnen.