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

fix ssec: missing base64 encoding and md5 hash #225

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

Conversation

gschei
Copy link

@gschei gschei commented Nov 10, 2024

This PR is based on #224 and replaces PR #217 (which has some code duplication and is not complete).

It fixes these issues:

  • the SSEC-CustomerKey is stored in the Velero Container as plain 32 byte key and must be base64 encoded before sending it to AWS.
  • as shown in PR 217 MD5 hash is missing for head/get/put operations

I tested this change successfully with an encrypted backup and restore with Velero and S3 Bucket.

@gschei gschei changed the title gschei/fix ssec base64 md5 fix ssec: missing base64 encoding and md5 hash Nov 10, 2024
@tonobo
Copy link

tonobo commented Nov 22, 2024

I'm pretty new to velero, i tried this mr locally and noticed there are issues while accessing the logs and futher contents from the bucket, probably caused by not passing the sse-c headers. am i holding it wrong or is there still something missing.

@@ -242,7 +247,7 @@ func readCustomerKey(customerKeyEncryptionFile string) (string, error) {
fileHandle.Close()

if nBytes != 32 {
return "", errors.Wrapf(err, "contents of %s (%s) are not exactly 32 bytes", customerKeyEncryptionFileKey, customerKeyEncryptionFile)
return "", fmt.Errorf("contents of %s (%s) are not exactly 32 bytes", customerKeyEncryptionFileKey, customerKeyEncryptionFile)
Copy link
Member

Choose a reason for hiding this comment

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

Is this necessary? This will mean the log trace won't propagate to velero pod logs with stack trace

Copy link
Author

Choose a reason for hiding this comment

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

The point of the fix is that err is always nil in this situation and errors.Wrapf here always returns nil, so to velero looks like key was read successfully (but is ""). I encountered this situation during testing, having a key < 32 bytes and no errors showed up in velero (but overall encryption did not work).

With this change, now the error is shown in the Backup-Resource correctly (but no stacktrace though).

Copy link
Member

Choose a reason for hiding this comment

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

ahh.. got it. Make sense. Here's a revised suggestion.

Suggested change
return "", fmt.Errorf("contents of %s (%s) are not exactly 32 bytes", customerKeyEncryptionFileKey, customerKeyEncryptionFile)
return "", errors.Errorf("contents of %s (%s) are not exactly 32 bytes", customerKeyEncryptionFileKey, customerKeyEncryptionFile)

Copy link
Author

Choose a reason for hiding this comment

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

thanks..PR updated

@@ -186,7 +189,9 @@ func (o *ObjectStore) Init(config map[string]string) error {
if err != nil {
return err
}
o.sseCustomerKey = customerKey
o.sseCustomerKey = base64.StdEncoding.EncodeToString([]byte(customerKey))
Copy link
Member

Choose a reason for hiding this comment

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

Why are we encoding string to []byte then string again?

Copy link
Author

Choose a reason for hiding this comment

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

So, your idea is to change the function readCustomerKey to return a []byte instead of a string? Would make sense, I can update the PR for it.

Copy link
Member

Choose a reason for hiding this comment

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

I see now that the description says "<your_b64_encoded_32byte_string>" which is wrong, the key must be plain and is then encoded to base64 before sending it.

Does "the key must be plain" holds true prior to this PR? Are we creating breaking change from customerKey is base64 -> customerKey must be plain?

Is the reason you are adding encoding here is because you wanted plain key instead?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, the question is, should the key be stored plain or base64 encoded in the container? I assume plain because here

keyBytes := make([]byte, 32)

we read exactly 32 bytes. If the key would be base64 encoded, then it would be longer then 32 bytes, and the mentioned line would be incorrect and needs to be fixed.

@gschei
Copy link
Author

gschei commented Nov 23, 2024

I'm pretty new to velero, i tried this mr locally and noticed there are issues while accessing the logs and futher contents from the bucket, probably caused by not passing the sse-c headers. am i holding it wrong or is there still something missing.

To test it successfully, you must mount the encryption key into the velero container (eg. from a secret) and then reference the file containing the key in the config of the BackupStorageLocation. See description here: https://github.com/vmware-tanzu/velero-plugin-for-aws/blob/main/backupstoragelocation.md.

I see now that the description says "<your_b64_encoded_32byte_string>" which is wrong, the key must be plain and is then encoded to base64 before sending it.

Signed-off-by: Gilbert Scheiblhofer <[email protected]>
@tonobo
Copy link

tonobo commented Nov 24, 2024

@gschei you're right that's not my concern. I get it to work after checking the actual code not the problem, but once i enable the encryption part i'm unable to call velero backup logs for example. I always get an common error message as it's not passing the credentials somewhere. Local and within the container.

@tonobo
Copy link

tonobo commented Nov 24, 2024

EDIT: Nevermind, handled totally differnent

besides that, the internal kopia data mover seems to have trouble as well.

 data path backup failed: Failed to run data path service for DataUpload
    daily-pvc-backup-20241124210813-lkshf: error to initialize data path: error to
    boost backup repository connection backups-apps-kopia: error to connect backup
    repo: error to connect repo with storage: error to connect to repository: repository
    not initialized in the provided storage'

Can you confirm csi data movement works with sse-c backup locations?

P.S by dropping the sse-c key at all, it just works.

@gschei
Copy link
Author

gschei commented Nov 25, 2024

@gschei you're right that's not my concern. I get it to work after checking the actual code not the problem, but once i enable the encryption part i'm unable to call velero backup logs for example. I always get an common error message as it's not passing the credentials somewhere. Local and within the container.

Yes I see velero backup logs creates a DownloadRequest Resources which does not contain the Encryption Headers. Looks like object_store.CreateSignedURL needs to be extended too with Encryption Parameters.

@gschei
Copy link
Author

gschei commented Nov 26, 2024

It looks like AWS getSignedUrl dies not support SSECustomerKey, see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property

Note:

Not all operation parameters are supported when using pre-signed URLs. Certain parameters, such as SSECustomerKey, ACL, Expires, ContentLength, or Tagging must be provided as headers when sending a request. If you are using pre-signed URLs to upload from a browser and need to use these fields, see createPresignedPost().

@kaovilai
Copy link
Member

getSignedUrl

is currently required until vmware-tanzu/velero#6167 gets implemented.

@gschei
Copy link
Author

gschei commented Dec 10, 2024

One additional remark regarding the encryption key: original implementation ac31cd9 (from feb/22) stated that the key should be a "32 byte string". Later (oct/23) a863977 a documentation update was done which apparently was wrong because it contradicts the original statement, saying that the key should now be a "base64 encoded 32 byte string" which is not correct, because within the code only the first 32 bytes are read anyway (and base64 endoding will make the string longer).

From my point of view current implementation is not working at the moment - once due to missing base64 (or alternatively correct parsing of base64 encoded key) and also due to missing md5 hash - a problem which appeared later.

velero backup logs statement is generally not working for encrypted backups and will also not work after merging this PR. But creation and restore of encrypted backups would work now.

@kaovilai @reasonerjt Could you please have a look at this again and merge if OK?

@kaovilai
Copy link
Member

Please update readme and or backupstoragelocation.md to reflect this PR or the current state of using customerKey since you're probably most versed in how to use this PR as is.

@kaovilai
Copy link
Member

# Specify the file that contains the SSE-C customer key to enable customer key encryption of the backups
# stored in S3. The referenced file should contain a 32-byte string.
#
# The customerKeyEncryptionFile points to a mounted secret within the velero container.
# Add the below values to the velero cloud-credentials secret:
# customer-key: <your_b64_encoded_32byte_string>
# The default value below points to the already mounted secret.
#
# Cannot be used in conjunction with kmsKeyId.
#
# Optional (defaults to "", which means SSE-C is disabled).
customerKeyEncryptionFile: "/credentials/customer-key"

Is this accurate as is?

@gschei
Copy link
Author

gschei commented Dec 10, 2024

@kaovilai I clarified the description. The point is, when I base64 encode the key within the kubernetes secret, it will be automatically base64 decoded by kubernetes when mounted into the container.

# The customerKeyEncryptionFile points to a mounted secret within the velero container.
# Add the below values to the velero cloud-credentials secret:
# Eg. add to the velero "cloud-credentials" secret this entry with the base64 encoded key
# (will be decoded when the secret is mounted)
# customer-key: <your_b64_encoded_32byte_string>
Copy link
Member

Choose a reason for hiding this comment

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

Should this line be uncommented? or is the encoded customer-key only supposed to be populated by the object_store.go?

Copy link
Member

Choose a reason for hiding this comment

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

Can it be expected that someone put in encoded 32 byte string instead of specifying the file name in the key below this one?
If so

Suggested change
# customer-key: <your_b64_encoded_32byte_string>
customer-key: <your_b64_encoded_32byte_string>

Copy link
Author

Choose a reason for hiding this comment

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

The function readCustomerKey currently only supports reading the secret from a file mounted inside the velero container. So the example here shows how to put the key into the secret, therefore it is just a comment and not part of the BSL configuration. I think it make sense, not to provide the senstive key as parameter, but only as secret.

Copy link
Member

@kaovilai kaovilai left a comment

Choose a reason for hiding this comment

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

This lgtm. Just some clarifications. Thanks for the PR!

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.

3 participants