Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENHANCEMENT] Enable setting an encryption key to be used to encrypt/decrypt sensitive data #13

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

Gabrielopesantos
Copy link

@Gabrielopesantos Gabrielopesantos commented Feb 5, 2024

Description

Still ironing some things on this solution proposal, will add a description and mark the ticket as ready to be reviewed once I'm done.
If you see the PR in the meanwhile and think there's a better way to achieve this, feel free to comment.

Adds support for encryption_key and encryption_key_file values to be set, both "" by default. If encryption_key is set, the key is made available as an environment variable. If both values are set, the key is set on a secret, mount as a volume and made available to the perses process as a file. In cases encryption_key isn't set, the value of encryption_key_file, if set, is ignored.

At the time of development I thought of this approach or one where we would expect secrets with a given name to be present so they could be mount or loaded as env vars. I'm still not sure what would be better but feel free to leave feedback.

The PR doesn't add support auth providers as discussed but I intend to open another PR for it.

Checklist

  • Pull request has a descriptive title and context useful to a reviewer.
  • Pull request title follows the [<catalog_entry>] <commit message> naming convention using one of the following catalog_entry values: FEATURE, ENHANCEMENT, BUGFIX, BREAKINGCHANGE, IGNORE.
  • All commits have DCO signoffs.

@Gabrielopesantos Gabrielopesantos force-pushed the gabrielopesantos/helm-chart-update branch from c776b96 to c4a19be Compare February 5, 2024 23:46
@Gabrielopesantos Gabrielopesantos changed the title WIP - [ENHANCEMENT] Enable setting an encryption key to be used to encrypt/decrypt sensitive data [ENHANCEMENT] Enable setting an encryption key to be used to encrypt/decrypt sensitive data Feb 18, 2024
encryption key is loaded as a env var or file depending if encryption_key_file is set or not

Signed-off-by: Gabriel Santos <[email protected]>
@Gabrielopesantos Gabrielopesantos marked this pull request as ready for review February 18, 2024 22:56
@@ -191,4 +191,4 @@ datasources:
# plugin:
# kind: PrometheusDatasource
# spec:
# directUrl: https://prometheus.demo.do.prometheus.io
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) can we remove this change, just to keep the PR clean as possible?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, my editor added it I don't mind rolling back.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also an editor adding newlines in all files. I know that it can be a pain to manage so to me this not that a problem if you don't ^^

@@ -0,0 +1,17 @@
{{- if and .Values.config.security.encryptionKeyFile .Values.config.security.encryptionKey }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should provide the ability to users use an existing secret in the cluster.

  • Disable the secret creation
  • Provide the existing secret name and secret key to be mounted.

Copy link
Author

@Gabrielopesantos Gabrielopesantos Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any suggestions for what would it look like? I mentioned this in the PR's description but I'm not sure about the implementation. This gets a bit complex as there are many ways to make the key available to the perses process.

If encryptionKey is the only value provided then we set it in the config.yaml file.
If encryptionKey and encryptionKeyFile are set, we create the secret and mount it on the pod.
If existingEncryptionKeySecret (or any other name) is given we just set the volume and volume mount in the statefulset with the information provided by the user.
But what about precedence? For me, the following makes sense but let me now what you think. Existing secret > Encryption key mount as file > Encryption key put in the config.yaml file.

P.S.
I was thinking of the following format for the existing secret information to be provided.

# -- Mount encryption key with an existing secret
existingEncryptionKeySecret:
  secretName: ""
  secretKey: ""

EDIT: If you see this before I finish with the approach that I'm trying, please ignore "WIP" commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the precedence you shared.

Note that you can't set encryptionKey and encryptionKeyFile at the same time in the Perses config otherwise the server will crashed because the config is inaccurate.

See https://github.com/perses/perses/blob/main/pkg/model/api/config/security.go#L55-L56

{{- if and .Values.config.security.encryptionKeyFile .Values.config.security.encryptionKey }}
encryption_key_file: {{ printf "%s/key" (.Values.config.security.encryptionKeyFile | trimSuffix "/") }}
{{- end }}
{{- if and (not .Values.config.security.encryptionKeyFile) .Values.config.security.encryptionKey }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but from my understanding if user provides a encryption key file he must to provide a encryption key.

Is that right @Nexucis ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's my understanding as well. But if an encryption key file is provided, and the file is mount on the pod, is there any reason to also add the key in the configuration file, config.yaml?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah encryptionKey and encryptionKeyFile are mutually exclusive but they are not mandatory.
If you don't provide it, there is one hardcoded that will be used.

@@ -102,6 +102,14 @@
"type": "boolean",
"default": false
},
"encryptionKey": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think makes sense add this values to the values.yaml as empty values, just to let users know it exists and is a possibility, as well as because the documentation is being created from the values file.

@nicolastakashi
Copy link
Contributor

Thanks for handle this @Gabrielopesantos, just left some comments, nice work 💪🏽

@Gabrielopesantos
Copy link
Author

Thanks for handle this @Gabrielopesantos, just left some comments, nice work 💪🏽

Thank you for the feedback, I will have a look as soon as I can. 👍

@Gabrielopesantos Gabrielopesantos force-pushed the gabrielopesantos/helm-chart-update branch from bc655ee to 110590f Compare April 7, 2024 22:45
@nicolastakashi
Copy link
Contributor

@Gabrielopesantos could you check CI?

@Gabrielopesantos Gabrielopesantos force-pushed the gabrielopesantos/helm-chart-update branch from 110590f to 0f0d417 Compare April 19, 2024 12:12
@Nexucis
Copy link
Member

Nexucis commented Apr 22, 2024

@Gabrielopesantos the CI detect an format issue. Probably an issue related to the {if} {end}
Helm is always a pain when running into this issue :(

@Gabrielopesantos Gabrielopesantos force-pushed the gabrielopesantos/helm-chart-update branch from 0f0d417 to 84ccd5d Compare April 25, 2024 12:15
@Gabrielopesantos
Copy link
Author

Gabrielopesantos commented Apr 25, 2024

My bad and thank you for the patience. I could have easily run helm lint locally and fixed these issues. Should be good to be reviewed now.

I'm not a fan of my implementation but couldn't think of another way to implement this logic of having precedence of values for the secret (even worse with go templates).

@Nexucis
Copy link
Member

Nexucis commented Apr 30, 2024

awesome thank you @Gabrielopesantos !

@celian-garcia and/or @nicolastakashi if you have time, could you please take a look ?
Thank you !

Copy link
Member

@celian-garcia celian-garcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, thanks for this PR. I am trying to review but it sounds complex to me.

If I understand properly:

  • if user give an encryptionKey or encryptionKeyFile it uses it in the config. That part is simple.

  • Now if the user give an encryptionKeyFile it means that we need mount the volume.
    Two solution : the user has a secret or the user want us to create a secret.

  • And if a user doesn't provide a file but provide a secret we want the file to be set.

  • Also I don't see a case where the encryptionKeyFile would exist without a k8s secret but maybe I'm wrong

Did I forget something? :D

If I try to translate it to a yaml self explanatory, I can draft a proposal that in my feeling would be less confusing for the user and easier to develop on your side. WDYT?

security: 
  encryptionKey: ""
  encryptionKeyFile: 
    activated: false # this is to ease the configuration if a user want to setup the secret but activate it or not easily
    secretName: '' # default to perses-full-name encryptionsecret
    secretKey: 'key' # (setting the default in the values file directly would prevent from forgetting a "| default" in the code)

Note:
For the mutually exclusive fields, you could do a check with helm on top of the templates using them. It would save you from some checks.

{{- if and .Values.security.encryptionKey .Values.security.encryptionKeyFile.activated }}
  {{- fail ( printf "encryptionKey and encryptionKeyFile are mutually exclusive" ) }}
{{- end }}

@@ -191,4 +191,4 @@ datasources:
# plugin:
# kind: PrometheusDatasource
# spec:
# directUrl: https://prometheus.demo.do.prometheus.io
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also an editor adding newlines in all files. I know that it can be a pain to manage so to me this not that a problem if you don't ^^

@@ -12,6 +12,12 @@ data:
config.yaml: |-
security:
readonly: {{ .Values.config.security.readOnly }}
{{- if and .Values.config.security.encryptionKeyFile .Values.config.security.encryptionKey }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not possible as mutually exclusive

@@ -12,6 +12,12 @@ data:
config.yaml: |-
security:
readonly: {{ .Values.config.security.readOnly }}
{{- if and .Values.config.security.encryptionKeyFile .Values.config.security.encryptionKey }}
encryption_key_file: {{ printf "%s/key" (.Values.config.security.encryptionKeyFile | trimSuffix "/") }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not taking into account your override* fields

@Gabrielopesantos
Copy link
Author

Gabrielopesantos commented May 30, 2024

Hello, thanks for this PR. I am trying to review but it sounds complex to me.

If I understand properly:

  • if user give an encryptionKey or encryptionKeyFile it uses it in the config. That part is simple.
  • Now if the user give an encryptionKeyFile it means that we need mount the volume.
    Two solution : the user has a secret or the user want us to create a secret.
  • And if a user doesn't provide a file but provide a secret we want the file to be set.
  • Also I don't see a case where the encryptionKeyFile would exist without a k8s secret but maybe I'm wrong

Did I forget something? :D

If I try to translate it to a yaml self explanatory, I can draft a proposal that in my feeling would be less confusing for the user and easier to develop on your side. WDYT?

security: 
  encryptionKey: ""
  encryptionKeyFile: 
    activated: false # this is to ease the configuration if a user want to setup the secret but activate it or not easily
    secretName: '' # default to perses-full-name encryptionsecret
    secretKey: 'key' # (setting the default in the values file directly would prevent from forgetting a "| default" in the code)

Note:
For the mutually exclusive fields, you could do a check with helm on top of the templates using them. It would save you from some checks.

{{- if and .Values.security.encryptionKey .Values.security.encryptionKeyFile.activated }}
  {{- fail ( printf "encryptionKey and encryptionKeyFile are mutually exclusive" ) }}
{{- end }}

Hey @celian-garcia ,
Thanks for the feedback. Your suggestion simplifies the UX and also the wiring on the templates. Still, unless I misunderstood your suggestion I don't think encryptionKey and encryptionKeyFile.activated are mutually exclusive as you commented.

To ensure we are on the same page, the following are the ways I believe were discussed to make the encryption key available to perses.

  1. Set in encryption_key on the config file;
  2. New secret, holding encryption key, mount as a file somewhere in the deployment resource and encryption_key_file set in the config file;
  3. Existing secret, holding encryption key, mount as a file somewhere in the deployment resource and encryption_key_file set in the config file;

The following is how each method of setting the encryption key would be configured with your suggested YAML config.

security: 
  encryptionKey: "asdasdas"
  encryptionKeyFile: 
    activated: false
    secretName: ''
    secretKey: 'key'
security: 
  encryptionKey: "asdasdas"
  encryptionKeyFile: 
    activated: true
    secretName: ''
    secretKey: 'key'
  encryptionKey: ""
  encryptionKeyFile: 
    activated: true
    secretName: 'secret-asd'
    secretKey: 'key'

It works nicely for 1 and 3 and they don't have "mutually" exclusive fields used, however, for 2 encryptionKey and encryptionKeyFile.activated are needed at the same time for a new secret to be created.

Also, there's no way to set the path where the encryption key file would be mounted but that's not an issue as a default like /etc/perses/security/encryptionkey could be used.

@celian-garcia
Copy link
Member

Hey @celian-garcia , Thanks for the feedback. Your suggestion simplifies the UX and also the wiring on the templates. Still, unless I misunderstood your suggestion I don't think encryptionKey and encryptionKeyFile.activated are mutually exclusive as you commented.

To ensure we are on the same page, the following are the ways I believe were discussed to make the encryption key available to perses.

  1. Set in encryption_key on the config file;
  2. New secret, holding encryption key, mount as a file somewhere in the deployment resource and encryption_key_file set in the config file;
  3. Existing secret, holding encryption key, mount as a file somewhere in the deployment resource and encryption_key_file set in the config file;

The following is how each method of setting the encryption key would be configured with your suggested YAML config.

security: 
  encryptionKey: "asdasdas"
  encryptionKeyFile: 
    activated: false
    secretName: ''
    secretKey: 'key'
security: 
  encryptionKey: "asdasdas"
  encryptionKeyFile: 
    activated: true
    secretName: ''
    secretKey: 'key'
  encryptionKey: ""
  encryptionKeyFile: 
    activated: true
    secretName: 'secret-asd'
    secretKey: 'key'

It works nicely for 1 and 3 and they don't have "mutually" exclusive fields used, however, for 2 encryptionKey and encryptionKeyFile.activated are needed at the same time for a new secret to be created.

Also, there's no way to set the path where the encryption key file would be mounted but that's not an issue as a default like /etc/perses/security/encryptionkey could be used.

I'm reading myself again and I don't even know what I meant 😄 It's been a while.
Now reading myself I'm challenging the existence of encryptionKey field. It should always use a secret no?

I would even do now

encryptionKey:
  secret: ''   // Default value is {{perses full name}}
  useExistingSecret: false // by default it creates a secret
  key: 'key' // Default value that you've no reason to update if you don't have your own secret
  value: 'dfkskdfjhskdfjh' // this is the default value that you can use but not advised.

Then if you want to reuse a secret

encryptionKey:
  secret: 'myAlreadyExistingSecret' 
  useExistingSecret: true
  key: 'myEncryptionKey' 

If you want to create a secret with default props

encryptionKey:
  value: 'mysecretvalue'

If you want to create a secret with custom props

encryptionKey:
  secret: 'myNotExistingCustomSecretName' // will be created
  key: 'mySecretKey' // will be created
  value: 'mysecretvalue'

@nicolastakashi
Copy link
Contributor

Hey @Gabrielopesantos friendly ping, are you willing to work on it yet?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants