100 Days of Learning: Day 4 – Using Environment variables and Secrets in OpenFaaS

Photo by Zbynek Burival on Unsplash

Here is my Log book

Today I managed to learn more about OpenFaaS as well as refresh some Swift knowledge.

OpenFaaS

Learning action point: Is it possible to bundle up functions to be in the same docker container image? At the moment it looks like 1 function = 1 docker image

Supplying configuration values via environment variables

In Serverless for Everyone Else, Alex gives us an example of how environment variables can be supplied to the node.js function via the YAML file.

I am going to see if I can modify my Python function from yesterday to be configurable in the same way.

Added the section environment to my "helloworld.yml" file to configure how the JSON will be prettified.

version: 1.0
provider:
  name: openfaas
  gateway: http://192.168.64.4:8080
functions:
  helloworld:
    lang: python3
    handler: ./helloworld
    image: dockername/helloworld:latest

    environment:
        indent: 2
        sort_keys: false

Changed the "handler.py" to read the values for the indentation level and whether to sort the JSON keys from the environment variables. The default indentation level is 4 spaces and to not sort the keys.

import json
import os

def handle(req):
    indent = int(os.environ.get('indent', '4'))
    sort_keys = os.environ.get('sort_keys', 'false').lower() in ['true', 'yes', '1']
    data = json.loads(req)
    response = json.dumps(data, indent=indent, sort_keys=sort_keys)
    return response

Deploy

$ faas-cli up -f helloworld.yml
...
<Had a few issues because the YAML contained TABS in the wrong places>
...
Deployed. 200 OK.
URL: http://192.168.64.4:8080/function/helloworld.openfaas-fn

Test

$ curl --data-binary '{"z": 1, "a": 2, "c": 3, "b": 4}' --header "Content-Type: application/json" http://192.168.64.4:8080/function/helloworld
{
  "z": 1,
  "a": 2,
  "c": 3,
  "b": 4
}

So what is the benefit in using environment variables? It allows you to not hardcode values all over the place in your code. Also I noticed that creating the docker image is quicker when only the yaml has changed.

NOTE: Do not use environment variables to store secret information! See the official OpenFaaS documentation for more information. Also there is a really nice example in Serverless for Everyone Else that shows how to protect your API using a secret token.

Supplying secret values to your function

To explore this I am going to deploy a new function that will read a stored secret and return it back to the caller.

Note:

Secret name must start and end with an alphanumeric character and can only contain lower-case alphanumeric characters, ‘-‘ or ‘.’

# Remember to first mkdir the directory for the function!
╭ ~/Learning/faasd/expose                                                                [20:05:44]
╰ $ faas-cli new --lang python3 expose

Add the names of secrets you want access to the yaml file

version: 1.0
provider:
  name: openfaas
  gateway: http://192.168.64.4:8080
functions:
  expose:
    lang: python3
    handler: ./expose
    image: dockername/expose:latest

    secrets:
      - expose-me

I have added a secret with the name "expose-me" and thus our function will have access to it by the file "/var/openfaas/secrets/expose-me"

Edit the handler.py file

def handle(req):
    with open('/var/openfaas/secrets/expose-me') as f:
        secret = f.read()
    return secret

Deploy

$ faas-cli up -f expose.yml
...
Unexpected status: 400, message: unable to find secret: expose-me

Right ok, need to first actually add the secret to be used

$ faas-cli secret create expose-me --from-literal 'All your secrets have been exposed'
...
Creating secret: expose-me
Created: 200 OK

$ faas-cli secret list
NAME
expose-me

Deploy mk2

$ faas-cli up -f expose.yml
...
Deployed. 200 OK.
URL: http://192.168.64.4:8080/function/expose.openfaas-fn

Test

$ curl http://192.168.64.4:8080/function/expose
All your secrets have been exposed

Ouch! our secret was exposed. Let us see if we can change the secret.

$ faas-cli secret help
...
Available Commands:
  create      Create a new secret
  list        List all secrets
  remove      remove a secret
  update      Update a secret

$ faas-cli secret update expose-me --from-literal 'Honeypot'
Updating secret: expose-me
Updated: 200 OK

$ curl http://192.168.64.4:8080/function/expose
Honeypot

Nuke the exposed secret and hopefully the Python code will blow up.

$ faas-cli secret remove expose-me
Removed.. OK.

$ curl http://192.168.64.4:8080/function/expose
Honeypot
# Ouch! ok I wasn't expecting that. My guess is the /var/... file still exists

Learning action point: Will need to read the docs and possibly the code to see how the secrets are stored / communicated over to the faasd instance and why remove didn’t remove the stored file.


Swift: Mutating Objects is different than Mutating Structs

Objects (instances of a Class) are reference types.

Structs are value types

If you have a number of variables pointing to an object and you mutate the object, then each one of the variables will be pointing to the same mutated object. This could become dangerous if the object are being accessed from different threads and leads to the classic concurrency issue of a race condition.

Structs in Swift allow you to mutate without having the global side effects.

Mutating a struct only changes a single variable and not all variables with the same value.

var a = [3, 1, 2]
let b = a // a copy is made (but it will be copy-on-write)
a.sort() // mutating method
a // [1, 2, 3]
b // [3, 1, 2]

Members of a struct that are declared as var, is still protected based on how the variable is declared.

struct Event {
    var count: Int // member count can be mutated

    mutating func increment(_ amount: Int) {
        count += amount
    }
}

let e = Event()
e.increment(10) // This won't compile because the variable e is let

In order to mutate the member count even though it has been declared as var balance the variable doing the mutation needs to be declared with var

var e = Event()
e.increment(10) // This will now work because the variable is declared as var