Azure-Functions icon indicating copy to clipboard operation
Azure-Functions copied to clipboard

Improve documentation surrounding how function keys are managed when AzureWebJobsSecretStorageType=kubernetes

Open earlofhemsley opened this issue 10 months ago • 10 comments

First, I will refer you to this question I put on the microsoft community help fora a couple days ago.

Please read ^^^^ this question ^^^^ thoroughly as well as the answer I reported earlier today to get a good grip on the background of this request, and to understand how I ultimately found the solution.

The primary documentation I found on how to manage function app secrets in k8s was on this and this Microsoft Learn page.

It took me forever to find an example k8s manifest for how to declare the secret, but I eventually found one in a description of a PR here. I used this to come up with this manifest for my own application

apiVersion: v1
data:
  function.HttpTrigger.default: ++++++++
  host.master: ++++++++
kind: Secret
immutable: true
metadata:
  annotations:
    ecosystem: dotnet
    owning-team: vrbe
    pillar: VR
    runtime: Azure functions host runtime in docker
  creationTimestamp: '2025-03-25T18:57:09Z'
  labels:
    app: vrbe-auth
    argocd.argoproj.io/instance: vrbe-auth
    helm.sh/chart: common-1.0.0
  name: vrbe-auth-fn-keys
  namespace: vrbe
  resourceVersion: '336197458'
  uid: 26460870-c4a2-409c-aab6-0ea21fdd5d3b
type: Opaque

I can't say for sure exactly what was going on and what was causing that 500 since I have no context inside the transport that modifies the k8s secret or that routes requests from the http entry down to the function, but my best guess is that my application runtime was unable to update this k8s secret, and therefore, the application container was facing errors.

There are a few problems with this:

  • I would expect the container runtime to report problems interacting with k8s in the form of a load of error logs if it cannot modify the a kubernetes secret. There was no visibility into what the problem even was by reviewing application logs. There was simply nothing there.
  • If there is a logging configuration one can set up in order to get these logs to be reported to the console, then the logging documentation makes no mention of it.
  • There is no documentation inside of the linked Microsoft Learn documents as to what the data of the secret should be. I pulled host.master and function.HttpTrigger.default from a 6-year-old PR, and it magically seemed to work. There needs to be improved documentation about what would constitute well-formed k8s secret data, not to mention a huge warning that the immutable flag on the manifest cannot be set.

earlofhemsley avatar Mar 25 '25 22:03 earlofhemsley

@earlofhemsley I to have been trying get around the 500 error issue and can't seem to figure out how exactly this is supposed to work. Sorry, I am new to running azure functions on k8s. Trying to figure out how exactly the deployment will find the secret and where it will mount. Do you mind sharing a sampledeployment.yaml and host.json or local.settings.json?

additional context, I am just using kubectl apply in my local k8s to test it

paravatha avatar Mar 27 '25 22:03 paravatha

I also noticed this comment which I am guessing could work as well https://github.com/Azure/azure-functions-host/issues/4147#issuecomment-477442831

paravatha avatar Mar 27 '25 22:03 paravatha

@paravatha thanks for responding!

I took a best guess based on this documentation (previously linked as well) and this seems to be all you need to do in the deployment configuration to make the container try to find a k8s secret to use as its function key store

apiVersion: apps/v1
kind: Deployment
metadata: {
  # ... omitted ...
}
spec:
  # ... metadata, other things ...
  template:
    spec:
      containers:
        # ... image, other things ...
        - env:
            - name: AzureWebJobsSecretStorageType
              value: kubernetes
            - name: AzureWebJobsKubernetesSecretName
              value: vrbe-auth-fn-keys
            # many other env vars
status: {
  # ... omitted ...
}

here's the live (read: working) manifest for the k8s secret vrbe-auth-fn-keys

apiVersion: v1
data:
  functions.userauthredirect.default: ++++++++
  host.master: ++++++++
kind: Secret
metadata:
  annotations:
    ecosystem: dotnet
    owning-team: vrbe
    pillar: VR
    runtime: Azure functions host runtime in docker
  creationTimestamp: '2025-03-25T18:57:09Z'
  labels:
    app: vrbe-auth
    argocd.argoproj.io/instance: vrbe-auth
    helm.sh/chart: common-1.0.0
  name: vrbe-auth-fn-keys
  namespace: vrbe
  resourceVersion: '336197458'
  uid: 26460870-c4a2-409c-aab6-0ea21fdd5d3b
type: Opaque

that appears to be good enough, at least in my case, to get the function app to reach out to the secret for function key management.

I also noticed this comment which I am guessing could work as well https://github.com/Azure/azure-functions-host/issues/4147#issuecomment-477442831

if i hadn't been able to make the k8s secret work like I did, I probably would have pursued mounting a directory in my container runtime containing a file that the function app runtime could manipulate, similar to what's been done in that linked issue. I didn't get to that point, though.

earlofhemsley avatar Mar 28 '25 17:03 earlofhemsley

@earlofhemsley thanks for the quick reply. I got it working with mount path this morning, but not with this approach you tried.

            - name: AzureWebJobsSecretStorageType
              value: kubernetes
            - name: AzureWebJobsKubernetesSecretName
              value: vrbe-auth-fn-keys

Maybe, I messed up my config, Will try again and report back

paravatha avatar Mar 28 '25 17:03 paravatha

@earlofhemsley works now. Thanks a lot!!!

One additional thing I had to do in my setup as I didn't have it. the service account used by the pod needs these permissions verbs: ["get", "list", "patch", "update"] on secrets

not verbs: ["create","list","patch","update","get","list"] mentioned here https://github.com/Azure/azure-functions-host/issues/9049

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: az-func-api-role
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "patch", "update"]
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: az-func-api-rolebinding
subjects:
  - kind: ServiceAccount
    name: az-func-api-sa
roleRef:
  kind: Role
  name: az-func-api-role
  apiGroup: rbac.authorization.k8s.io

so, I had to create role and role-binding for that service account

paravatha avatar Mar 28 '25 21:03 paravatha

so, I had to create role and role-binding for that service account

sounds like this would be excellent information to include in whatever improved documentation comes of this. Thanks @paravatha

earlofhemsley avatar Mar 29 '25 00:03 earlofhemsley

@earlofhemsley did you ever run into this 500 internal issue mentioned here? https://github.com/Azure/azure-functions-host/issues/10740

Did you see 500 errors when you enabled detailed logging ?

paravatha avatar Apr 01 '25 18:04 paravatha

@earlofhemsley did you ever run into this 500 internal issue mentioned here? Azure/azure-functions-host#10740

That sounds very close to my issue, except that after i removed the immutable attribute from the secret, the azure functions runtime reached out to the secret and modified it to place a function key for the route that I had just called. While it was immutable, though, that is the behavior that I observed.

Did you see 500 errors when you enabled detailed logging ?

Detailed logging whaaaaat? If there's a way to enable detailed logging that I didn't already do, please tell me what that is and how. I configured logging down to Trace for everything I knew how to configure. See the original microsoft community help post I made for more info on how I configured logging.

earlofhemsley avatar Apr 01 '25 21:04 earlofhemsley

Detailed logging whaaaaat? If there's a way to enable detailed logging that I didn't already do, please tell me what that is and how. I configured logging down to Trace for everything I knew how to configure. See the original microsoft community help post I made for more info on how I configured logging.

Apologies, I misread/mis-understood your comment above. Enabling different levels as below didn't provide any insights in to the 500 Internal Server Error. Thanks again for your help!

    "logging": {
      "logLevel": {
        "default": "Trace",
        "Function": "Trace",
        "Host.Results": "Trace",
        "Host.Aggregator": "Trace",
        "Azure.Core": "Trace",
        "Microsoft": "Trace",
        "Worker": "Trace"
      },   

paravatha avatar Apr 01 '25 22:04 paravatha

Documenting different options for posterity for anyone running into similar issues References: https://learn.microsoft.com/en-us/azure/azure-functions/function-keys-how-to?tabs=azure-portal#manage-key-storage https://www.github.com/Azure/azure-functions-host/pull/4462#issue-445268179

Option 1 - This requires additional permissions to the service account, the app creates api keys on the fly.

Downside: not a good idea due to Enforcing least privilege access in Kubernetes environments pattern. this doesn't make sense to me, bit of a head scratcher

role.yaml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: az-func-api-role
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create","list","patch","update","get","list"]

role-binding.yaml

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: az-func-api-rolebinding
subjects:
  - kind: ServiceAccount
    name: az-func-api-sa
roleRef:
  kind: Role
  name: az-func-api-role
  apiGroup: rbac.authorization.k8s.io

part of deployment.yaml

      serviceAccountName:  az-func-api-sa
      containers:
        - name: api-container
          image: registry.azurecr.io/az-func-api:test
          env:
            - name: AzureWebJobsSecretStorageType
              value: kubernetes
            - name: AzureWebJobsKubernetesSecretName
              value: az-func-api-keys      

Option 2 - mount api keys as a file.

Downside: not a good idea if you are using a key vault lke hashicorp vault, need to maintain api keys in 2 places (1st as a file for the api server app, 2nd as a key to the client app). AzureWebJobsSecretStorageType:files is not recommended as its an old pattern https://learn.microsoft.com/en-us/azure/azure-functions/function-keys-how-to?tabs=azure-portal#manage-key-storage

secrets.yaml

apiVersion: v1
kind: Secret
metadata:
  name: az-func-api-keys
type: Opaque
stringData:
  host.json: |-
    {
      "masterKey": {
        "name": "master",
        "value": "masterkey",
        "encrypted": false
      },
      "functionKeys": [ ]
    }

part of deployment.yaml

          env:
          - name: AzureWebJobsSecretStorageType
            value: files
          volumeMounts:
          - name: functions-keys
            mountPath: "/azure-functions-host/Secrets"
            readOnly: true   
      volumes:
        - name: functions-keys
          secret:
            secretName: az-func-api-keys    

Option 3 - mount api keys as key-value pairs

This is a kind of a hybrid approach, which doesnt require additional privelges and api keys can be maintained in a single place in the vault, and uses AzureWebJobsSecretStorageType: kubernetes approach

secrets.yaml

apiVersion: v1
kind: Secret
metadata:
  name: az-func-api-keys
type: Opaque
data:
  host.master: base64encodedkey

part of deployment.yaml

          env:
            - name: AzureWebJobsSecretStorageType
              value: kubernetes
          volumeMounts:
            - mountPath: /run/secrets/functions-keys
              name: functions-keys
              readOnly: true   
      volumes:
        - name: functions-keys
          secret:
            secretName: az-func-api-keys    

paravatha avatar Apr 02 '25 13:04 paravatha