Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre faas

Cheap man's secret handling

I run a very cheap home serv­er. How cheap? Very, very cheap. Sub Rasp­ber­ry Pi 4 cheap.

It runs a ton of ser­vices, and it al­so works as my "Func­tion­s" serv­er.

What is a func­tions server?

It's a cheap man's AWS Lamb­da which al­lows me to cre­ate small "ser­vices" like this, and de­ploy them au­to­mat­i­cal­ly. It's re­al­ly a game chang­er for sim­ple code avail­abil­i­ty, and it's my favourite way to share func­tion­al­i­ty with oth­er­s.

But some­times a ser­vice re­lies on a 3rd par­ty API, and it needs things like a to­ken to be avail­able. Faasd sup­ports this us­ing their se­cret API. You cre­ate a se­cret like this:

faas-cli secret create whatever

And when you declare in your functions.yml that your function needs a secret:

myfunc:
  lang: python3-fastapi
  handler: ./myfunc
  secrets:
  - whatever

Your code reads a file in /var/openfaas/secrets/whatever and that's all, there is a secret on the server, your app can see it, it's not in the app's code, all good.

Ex­cept ... what hap­pens if you need to re­de­ploy faas­d? You will need to cre­ate the se­cret again! So you need to keep the se­cret some­where.

So­lu­tion: pass

I already use pass to keep many passwords, it's easy to also put secrets there. It manages everything using a git repo, so it's a known factor. You can even do things like add them all inside a faasd/ folder and then recreate them using scripts, like this:

pass faasd/whatever | faas-cli secret create whatever

pass will ask for your mas­ter passphrase, se­cret cre­at­ed. You can even pub­lish your pass re­po since ev­ery­thing in it is en­crypt­ed with gpg, so no­body can re­al­ly read it (don't do that).

So, this so­lu­tion us­es:

  • pass
  • gpg
  • git
  • faasd
  • shell
  • what­ev­er lan­guage and frame­work you use in your code

And ev­ery­thing is seam­less!

I think this is a nice ex­am­ple of how ran­dom tools can con­nect with each oth­er be­cause they all fol­low the unix con­ven­tion about mov­ing things around as tex­t.

New minisite: book covers

Since I wrote tapi­ta to au­to­mat­i­cal­ly cre­ate book cov­er­s, it was ab­surd­ly easy to turn it in­to a site where you can cre­ate book cov­er­s.

So, you can go to Cov­er­s.ralsi­na.me and cre­ate book cov­er­s.

Fun part: this is the whole back­end for the site:

from json import loads
from tapita import Cover
from io import BytesIO
import base64


def handle(req):
    """handle a request to the function
    Args:
        req (str): request body

    {
        "title": "foo",
        "subtitle": "bar",
        "author": "bat",
    }
    """
    try:
        args = loads(req)
    except Exception:
        return "Bad Request", 400

    c = Cover(**args)
    byte_arr = BytesIO()
    c.image.save(byte_arr, format="JPEG")

    return (
        f'<img src="data:image/jpeg;base64, {base64.b64encode(byte_arr.getvalue()).decode("utf-8")}">',
        200,
        {"Content-Type": "text/html"},

CORS config for FaaS

Be­cause I want to be able to de­ploy ran­dom python code eas­i­ly to my own server, I have set­up a "Func­tion as a Ser­vice" thing, called faasd (think of it as poor peo­ple's AWS lamb­da). More de­tails on how, why and how it turned out will come in the fu­ture. BUT: this ex­plains how to fix the un­avoid­able headache CORS will give you.

Sit­u­a­tion:

  • The Faasd serv­er runs in some ma­chine, which is prox­­ied by a Ng­inx serv­er avail­able at http­s://­­faas­d.ral­si­­na.me

  • Apps are just HTML pages some­where in­­­side ei­ther http­s://ral­si­­na.me or some oth­­er sim­i­lar do­­main.

What will happen?

You will set­up your func­tion, test it out us­ing curl, be hap­py it work­s, then set it up in your web app and get an er­ror in the con­sole about how CORS is not al­low­ing the re­quest.

What is CORS and why is it annoying me?

CORS is a way for a ser­vice liv­ing in a cer­tain URL to say which oth­er URLs are al­lowed to call it. So, if the app are in, say, http­s://nom­bres.ralsi­na.me and the func­tion lives in http­s://­faas.ralsi­na.me then the ORI­GIN for the app is not the same as the ORI­GIN for the func­tion, so this is a "Cross Ori­gin Re­quest" and you are try­ing to do "Cross Ori­gin Re­source Shar­ing" (CORS) and the brows­er won't let you.

How do I fix it?

There are a num­ber of fix­es you can try, but they all come down to the same two ba­sic ap­proach­es:

Option 1

Make it so the re­quest is not cross-­source. To do that, move the func­tion some­how in­to the same URL as the page, and bob's your un­cle.

So, just change the proxy con­fig so nom­bres.ralsi­na.me/­func­tions is prox­ied to the faasd server's /func­tions and change the page to use a re­quest that is not cross-o­rig­in, and that's fixed.

I don't want to do this be­cause I don't want to have to set­up the proxy dif­fer­ent­ly for each ap­p.

Option 2

Have the func­tion re­turn a head­er that says "Ac­cess-­Con­trol-Al­low-O­rig­in: some­thing". That "some­thing" should be the ori­gin of the re­quest (in our ex­am­ple nom­bres.ralsi­na.me) or "*" to say "I don't care".

So, you may say "Fine, I'll just add that head­er in my re­sponse and it will work!". Oh sweet sum­mer child. That will NOT work (at least not in the case of Faas­d)

Why?

Be­cause web browsers don't just make the re­quest they want and then look at the head­er­s. They do a spe­cial pre­flight re­quest, which is some­thing like "Hey, server, if I were to ask you to give me /func­tion­s/what­ev­er from this orig­in, would you give me a CORS per­mis­sion or not?"

That re­quest is done us­ing the OP­TIONS HTTP method, and Faasd (and, to be hon­est, most web frame­work­s) will not process those by pass­ing them to your code.

So, even if your func­tion says CORS is al­lowed, you still will get CORS er­rors.

You can see this if you ex­am­ine your browser's HTTP traf­fic us­ing the de­vel­op­er tool­s. There will be an OP­TIONS pre­flight re­quest, and that one does­n't have the head­er.

So, the eas­i­est thing is to add those in the proxy.

So, in my case, in the prox­y's ng­inx.­con­f, I had to add this in "the right place":

  add_header 'Access-Control-Allow-Origin' '*';

What is the right place will vary de­pend­ing on how you have con­fig­ured things. But hey, there you go.


Contents © 2000-2023 Roberto Alsina