Skip to content

Enable map key sorting in utils.FastMarshal#10070

Merged
timothyb89 merged 7 commits intomasterfrom
timothyb89/cas-sort-map-keys
Feb 7, 2022
Merged

Enable map key sorting in utils.FastMarshal#10070
timothyb89 merged 7 commits intomasterfrom
timothyb89/cas-sort-map-keys

Conversation

@timothyb89
Copy link
Copy Markdown
Contributor

This enables map key sorting during JSON serialization to ensure CompareAndSwap opertions work consistently if the resource in question has embedded map values (e.g. labels).

Note that this is hypothetically a breaking change: it makes comparisons consistent, so if it saves a variant that consistently fails a CompareAndSwap(), it will... fail consistently rather than sometimes work. In practice, today only CertificateAuthority resources use CompareAndSwap() and under normal circumstances they never have labels attached.

In the unlikely event that some resource is affected, a load/store should save it with sorted key ordering and fix the problem.

This enables map key sorting during JSON serialization to ensure
`CompareAndSwap` opertions work consistently if the resource in
question has embedded map values (e.g. labels).

Note that this is hypothetically a breaking change: it makes
comparisons consistent, so if it saves a variant that consistently
fails a `CompareAndSwap()`, it will... fail consistently rather
than sometimes work. In practice, today only `CertificateAuthority`
resources use `CompareAndSwap()` and under normal circumstances they
never have labels attached.

In the unlikely event that some resource is affected, a load/store
should save it with sorted key ordering and fix the problem.
@nklaassen
Copy link
Copy Markdown
Contributor

I think that CompareAndSwapCertAuthority is the only use of CompareAndSwap, and I really hope that nothing else is comparing serialized strings in the backend.

I am pretty sure that we have never set any labels on CertAuthorities, but to play it safe you could add a check to CompareAndSwapCertAuthority that no labels are set. If there are, since I don't expect to see this, logging a helpful warning is probably enough. In theory you could get creative with backend locks and write the sorted version to the backend but that's probably overkill.

@espadolini
Copy link
Copy Markdown
Contributor

I might be adding a label in the metadata of CertAuthority soon; perhaps the warning could be added whenever len(ca.Metadata.Labels) >= 2? There should be no ordering problem when it's just one key, right?

This attempts to mitigate compare-and-swap failures if CAs have
multiple labels by logging a warning instructing users to re-save
their CertificateAuthority resource.
@timothyb89
Copy link
Copy Markdown
Contributor Author

That's reasonable, it now logs a warning if comparisons fail and multiple labels are present.

Comment thread lib/utils/jsontools.go Outdated
// sorting to ensure CompareAndSwap checks consistently succeed.
var SafeConfig = jsoniter.Config{
EscapeHTML: false,
MarshalFloatWith6Digits: true, // will lose precession
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.

Precession is a change in the orientation of the rotational axis of a rotating body.

I realize that this is taken right from the definition of jsoniter.ConfigFastest but maybe we don't need to also mirror the misspelled comments 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

oh geez I'm embarrassed that I missed the typo, will fix!

Comment thread lib/services/local/trust.go Outdated
if err != nil {
if trace.IsCompareFailed(err) {
if len(existing.GetMetadata().Labels) >= 2 {
log.Warn("compare-and-swap comparison failed on certificate authority with multiple labels; it may need to be re-saved to sort map keys")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Any warnings or errors we log should be actionable and easy to understand by an end user, not just developers.

If a user sees this warning message, what do we expect them to do about it?

  • if we don't expect them to do anything, then it's probably not a warning
  • if we do expect them to do something, let's make the message more clear as to what that is

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair point, I've changed it to now output a message like the following:

comparison failed on certificate authority with multiple labels; if this occurs consistently, try re-saving the resource: tctl get cert_authority/user/foo.example.com --with-secrets > ca.yaml && tctl create -f ca.yaml && rm ca.yaml

Copy link
Copy Markdown
Contributor

@fspmarshall fspmarshall left a comment

Choose a reason for hiding this comment

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

LGTM. Determinism is always good.

For those who are curious, there is a "correct" way to compare and swap under non-deterministic serialization. CAs just didn't do it for some reason:

  1. Load existing value.
  2. Compare expected value to deserialized version of existing value.
  3. Call Backend.CompareAndSwap with the value loaded in step 1.

Simply having determinism is better/faster though.

@timothyb89 timothyb89 enabled auto-merge (squash) February 7, 2022 23:14
@timothyb89 timothyb89 merged commit f216605 into master Feb 7, 2022
@timothyb89 timothyb89 deleted the timothyb89/cas-sort-map-keys branch February 7, 2022 23:32
@fspmarshall fspmarshall mentioned this pull request Feb 14, 2022
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.

5 participants