Skip to content

Transparent decryption with metadata replacing - #515

Merged
Lagovas merged 54 commits into
masterfrom
lagovas/encoding-data-according-to-data-type
Mar 29, 2022
Merged

Lagovas merged 54 commits into
masterfrom
lagovas/encoding-data-according-to-data-type

Conversation

@Lagovas

@Lagovas Lagovas commented Mar 21, 2022

Copy link
Copy Markdown
Collaborator
  • Added processing DataRowDescription and ParameterDescriptionData. In DataRowDescription now we replace OID values that tell drivers which data types will be returned. In ParameterDescriptionData we replace OIDs for parameters in prepared statements. To tell apps that they should pass string value instead of binary (that will be changed on Acra side)
  • Extend encryptor config with two new fields: default_data_value and data_type. Last values generated via protobuf in similar way as token type.
  • Added support of text format values in binary protocols (prepared statements). Previously was supported only binary formatted values.
  • Refactored QueryDataEncryptor to re-use functions related to parsing placeholders and saving encryptor's settings in context/client session values
  • Extended ClientSession interface to provide KV interface and store session related data for client/db packet handlers
  • Removed legacy acra-log-verifier config file from configs folder
  • Added updating context (ctx) with flag after successful decryption (base.[MarkDecryptedContext|IsDecryptedFromContext]). Needs for replacing with default values binary data types that hard to determine when they was untouched
  • Removed legacy interface method SubscribeOnColumnDecryption and all related methods of proxies due to not using
  • Refactored PgSQLDataEncoderProcessor. Now it used not only for tokenization, but for decoding all input data from database and encoding before sending to client. Also removed this logic from PostgresqlDecryptor which now responsible only for database packet parsing. All other data manipulations moved to OnQuery/OnBind/OnColumn subscribers
  • Additionally removed DecodedData wrapper for Column structs used by decryptor

It's ready to review. Probably last changes will be related to encryptor's config validation and updating changelogs (after that I will change it from status Draft). So welcome to review.

Checklist

wrap encoding functions with wrapper that do nothing with valid strings
user struct instead of interface for QueryDataItem to avoid extra nil checks with reflection
weren't used by any component and just produce unused code and complicate
reading
…ing/decoding

operations over interesting data for decryption/detokenization received
from database
extend encryptor, now find ColumnEncryptionSettings for queries from Parse
Packet with placeholders and save it in ClientSession to use it in
ParameterDescription packet followed by Parse packet (and also for
Bind packet but there we already have query to re-parse it)
update OID values according to ColumnEncryptionSetting
change value encoding flow for encrypted integers
Remove encoding/decoding logic from Column struct, use only
from PostgresqlEncoderDecoder and BoundValue encodings
- don't use DecodedData struct
- store raw data on protocol handler level
- encode/decode data in OnColumn handler on higher level
fix parameter description updates
@Lagovas
Lagovas marked this pull request as ready for review March 24, 2022 12:06

@G1gg1L3s G1gg1L3s left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

And a few comments and wishes about git history: I think it would be better to split this PR into multiple ones. For example, each item of changes in the description could easily get its own PR. I don't mind if these small PRs would contain only change of a single line of code, because it would greatly improve the quality and speed of reviews: it's easier to separate changes and think why they are important on their own. Also, I like reading not only the final changes, but all commits one by one, to understand the logic about how and reasons why something is changing. But it was hard, because the commits often contain multiple changes at once, or are the parts of other commits. Very often they, beside some changes, they contain formatting fixes or commented code. For formatting I would recommend setup something like format on save or format on commit or something similar. Also I recommend reading about best practices about commit messages https://cbea.ms/git-commit

P.S. Sorry if some comments are outdated. They were written during the review of commits one by one, so some changes could happen in the next commits, that weren't reviewed yet.

Comment thread encryptor/dbDataCoder.go Outdated
Comment thread encryptor/dbDataCoder.go Outdated
Comment thread encryptor/config/encryptionSettings.go
Comment thread encryptor/config/schemaStore_test.go
Comment thread utils/utils.go
Comment thread utils/utils.go Outdated
Comment thread decryptor/postgresql/data_encoder.go
Comment thread tests/random_utils.py
Comment thread decryptor/postgresql/proxy.go Outdated
Comment thread encryptor/queryDataEncryptor.go Outdated
for j, value := range valTuple {
columnName := columnsName[j]
if changedValue, err := encryptor.encryptExpression(ctx, value, schema, columnName); err != nil {
columnName := columnsName[j%len(columnsName)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is there a modulo operation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the vscode plugin that I use there is a support of additional diagnostics about gc-escapes and small optimizations that guarantee to apply. And when I change this to just j, this what the vscode shows:
image

That means the compiler was able to deduce that j is in bounds of the columnsName slice.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

we use module because there should be not more than declared columns count:
insert into table1(col1, col2, col3) values (val1, val2, val3), (val4, val5, val6);
There is we have 3 columns and 3 tuples. Every tuple can have 3 values. If more than it's invalid behavior and we can skip it and pass as is to database. Because database will deny such query. But in valid case we are safe and map value to correct column.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, this is reasonable. But in my opinion, it would be better to just check that if j > len(columnNames) then just skip this iteration with continue. At least we will not spend time encrypting extra values.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

agree, updated

@Lagovas Lagovas left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

And a few comments and wishes about git history: I think it would be better to split this PR into multiple ones. For example, each item of changes in the description could easily get its own PR. I don't mind if these small PRs would contain only change of a single line of code, because it would greatly improve the quality and speed of reviews: it's easier to separate changes and think why they are important on their own. Also, I like reading not only the final changes, but all commits one by one, to understand the logic about how and reasons why something is changing. But it was hard, because the commits often contain multiple changes at once, or are the parts of other commits. Very often they, beside some changes, they contain formatting fixes or commented code. For formatting I would recommend setup something like format on save or format on commit or something similar. Also I recommend reading about best practices about commit messages https://cbea.ms/git-commit

P.S. Sorry if some comments are outdated. They were written during the review of commits one by one, so some changes could happen in the next commits, that weren't reviewed yet.

agree with you, will try it in next my PRs. This feature wasn't clear at the start and a lot of things were changed during the development due to existing of a lot of components linked with each other. but looking at now I see that there a lot of pieces that could be splitted into separate PRs easily *(
you prefer that I will fix current comments, commit, wait approval and after that split it into the separate PRs and review one more time or we can finish it as is and keep it in mind in the future?)
p.s. about auto-format. I already have it. But it has a small sleep before applying (to not format on every new character) and sometimes when I switch into the next tab/window after changes my IDE fails to format code) So, I will add it to pre-commit hooks to avoid such problems.

Comment thread decryptor/postgresql/data_encoder.go
Comment thread encryptor/config/encryptionSettings.go
Comment on lines +346 to +347
// If the configuration file contains some unknown or unsupported token type,
// return some safe default.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

it validated here -

if err = common2.ValidateEncryptedType(dataType); err != nil {

And this function only as Getter for the field. Errors may occur in tests where such structs are initialized by value

Comment thread encryptor/dbDataCoder.go Outdated
Comment thread tests/random_utils.py
Comment thread utils/utils.go Outdated
Comment thread utils/utils.go
@G1gg1L3s

Copy link
Copy Markdown
Contributor

you prefer that I will fix current comments, commit, wait approval and after that split it into the separate PRs and review one more time or we can finish it as is and keep it in mind in the future?)

I think we can left this PR as is :)

Comment thread encryptor/config/encryptionSettings.go Outdated
Comment thread masking/common/patterns.go Outdated
@Zhaars
Zhaars self-requested a review March 29, 2022 12:19

@Zhaars Zhaars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good for me.

@G1gg1L3s G1gg1L3s left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beside one comment, everything is cool!

Comment thread encryptor/queryDataEncryptor.go Outdated
// collect placeholder in queries to save for future ParameterDescription packet to replace according to
// setting's data type
//
// 32 as initial size to avoid future re-allocation. looks like enough for a lot of apps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like a leftover comment

@Lagovas
Lagovas merged commit e522e23 into master Mar 29, 2022
size = 4
}
// convert back from text to binary
value, err := strconv.ParseInt(string(data), 10, 64)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Small question: in all other cases we first check that value is indeed decrypted (by calling base.IsDecryptedFromContext(ctx)), and if not, we fallback to returning default values. But in this case we check decryption by parsing the number. Does it have some special meaning or reason?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the main reason is that any encrypted blob has size > len('9223372036854775807') = 19. Our acrablock has 90+ bytes of overhead. Acrastructs even more. So no one supported ciphertext cannot be valid integer string literal. So there is enough to check that it's valid valid integer string literal.

@Lagovas
Lagovas deleted the lagovas/encoding-data-according-to-data-type branch May 19, 2022 20:37
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