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: Issue #9364 JSON config validation #10679

Open
wants to merge 32 commits into
base: master
Choose a base branch
from

Conversation

gsergey418alt
Copy link

Fixes #9364. Disallow unknown fields when updating the config with ipfs config
Before:

$ ipfs config --json -- Provider  '{"Foo":""}' &&  ipfs config --json -- Provider && echo ok
{
  "Foo": ""
}
ok
$

After:

$ ipfs config --json -- Provider  '{"Foo":""}' &&  ipfs config --json -- Provider && echo ok
Error: failed to set config value: failure to decode config: json: unknown field "Foo" (maybe use --json?)
$

@gsergey418alt gsergey418alt requested a review from a team as a code owner January 28, 2025 10:01
@gsergey418alt gsergey418alt mentioned this pull request Jan 28, 2025
3 tasks
@gsergey418alt gsergey418alt changed the title fix: Issue #9364 JSON config validation [skip changelog] fix: Issue #9364 JSON config validation Jan 28, 2025
@guillaumemichel
Copy link
Contributor

guillaumemichel commented Jan 28, 2025

Triage notes:

  • Changelog should be moved to 0.34
  • Strict config file parsing should be optional (CLI flag e.g ipfs daemon --allow-unknown-config-fields--disallow-unknown-config-fields, true by default) to avoid breaking existing users

@gsergey418alt
Copy link
Author

Yeah, you're right, that would break existing users, will fix that tomorrow.

@gsergey418alt gsergey418alt marked this pull request as draft January 28, 2025 21:18
@gsergey418alt gsergey418alt marked this pull request as ready for review January 29, 2025 06:33
Copy link
Contributor

@guillaumemichel guillaumemichel left a comment

Choose a reason for hiding this comment

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

Thanks @gsergey418alt for your contribution.

Could you add a flag to enable/disable strict config JSON parsing? e.g ipfs daemon --allow-unknown-config-fields, true by default.

This would allow existing users to pass that flag to continue using the same config, and would facilitate development when comparing different versions with the same config.

test/sharness/t0021-config.sh Show resolved Hide resolved
config/config.go Show resolved Hide resolved
@gsergey418alt
Copy link
Author

Docker commands still failing in CI, maybe some issue on your side? I don't see anything related to ipfs config.

@gsergey418alt
Copy link
Author

@guillaumemichel The existing users can keep using the same config, the validation is only done on the ipfs config command.

config/config.go Outdated
Comment on lines 141 to 142
// Convert config to a map, without using encoding/json.
func ReflectToMap(conf interface{}) map[string]interface{} {
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the reason to define a new function, and not use ToMap defined above?

Copy link
Author

@gsergey418alt gsergey418alt Jan 30, 2025

Choose a reason for hiding this comment

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

There's no way to get encoding/json package (which is used in the ToMap function) to ignore the struct tags like json:",omitempty" that hide the map fields. That means that the user wouldn't be able to set these fields, since this map is used to validate user's input from ipfs config. Had to use the reflect package to make that happen.

@gsergey418alt
Copy link
Author

Same stuff with the docker commands

@guillaumemichel
Copy link
Contributor

I just checked locally, and only this PR is failing the docker sharness tests. master doesn't have this issue.

@gsergey418alt
Copy link
Author

@guillaumemichel Yep, seen that test. I fixed the values to be valid, but it's still failing because the docker container isn't starting up.

@guillaumemichel
Copy link
Contributor

guillaumemichel commented Jan 30, 2025

Turns out Datastore.Path isn't a valid config anymore: old style datatstore config detected, which is why the docker tests were failing.

Just opened #10686 to address the outdated ipfs config --help.

@gsergey418alt
Copy link
Author

Awesome, good catch.

@guillaumemichel
Copy link
Contributor

I updated the ReflectToMap since it was missing some cases, and added a test for it.

@gsergey418alt could you please adapt CheckKey accordingly, and add extensive sharness tests e.g in t0021-config.sh covering all possible key types (e.g bool, int, string, list, nested struct, etc.) both in json and "." (Key1.Key2.Key3) format where possible.

@gsergey418alt
Copy link
Author

Good work, but you would need a way to distinguish between in-config maps and structure. In my implementation, this was done via the "map" value of a field, so when the validator encounters this field, it knows that the user is trying to set nested value inside a map and it discards the rest of key. I think I should've named the function something like GetValidationMap or something

@guillaumemichel
Copy link
Contributor

guillaumemichel commented Jan 31, 2025

you would need a way to distinguish between in-config maps and structure

@gsergey418alt could you expand on that? I am not sure to understand the difference in practice.

If your implementation works better, we can use it, I just want to cover all corner cases, and to test everything.

@gsergey418alt
Copy link
Author

gsergey418alt commented Jan 31, 2025

@guillaumemichel If we run ipfs config --json Routing.Routers.Test "{\"Parameters\":\"Test\",\"Type\":\"Test\"}", we need a way to know that .Test is a user-defined entry in the Routers map and isn't supposed to be validated.

@guillaumemichel
Copy link
Contributor

@guillaumemichel If we run ipfs config --json Routing.Routers.Test "{"Parameters":"Test","Type":"Test"}", we need a way to know that .Test is a user-defined entry in the Routers map and isn't supposed to be validated.

Ok I see. Would that work for maps that have nested types?

@gsergey418alt
Copy link
Author

@guillaumemichel Hmm, haven't thought about that actually. Maybe we could create a "ref" element inside that map that could be validated against if you want that covered.

@gsergey418alt
Copy link
Author

@guillaumemichel I've added sharness test for other data types, changed function name to GetValidationMap. I've also changed the behavior of the function for maps, as it was in my previous implementation, without validation for map-nested structures. I won't be active until Monday, so have a good weekend!

@guillaumemichel
Copy link
Contributor

Could you add a test for Gateway.PublicGateways and to set all PublicGateways parameters? if this works as expected, we should be good and there is no need to modify the reflect code further.

Have a nice weekend!

@gsergey418alt
Copy link
Author

@guillaumemichel I've solved the problem with validating structs inside maps, and it now works as expected. Added test for PublicGateways.

config/config.go Outdated Show resolved Hide resolved
@guillaumemichel
Copy link
Contributor

@guillaumemichel I've solved the problem with validating structs inside maps, and it now works as expected. Added test for PublicGateways.

Looks good thanks!

A sharness test is still failing, probably due to the following config command:

ipfs config --json Plugins.Plugins.peerlog.Config.Enabled true

Additionally, could you also add a test TestCheckKey in config_test.go testing a few possible keys?

@gsergey418alt
Copy link
Author

Added handling for interface{} in the config, like Plugins.Plugins.peerlog.Config.Enabled

config/config.go Outdated
Comment on lines 237 to 239
if v, ok := cursor.(string); ok && v == "any" {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

@gsergey418alt would that work? Using "any" as key seems strange.

Suggested change
if v, ok := cursor.(string); ok && v == "any" {
return nil
}
if cursor == nil {
return nil
}

Copy link
Author

Choose a reason for hiding this comment

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

I think I'm using it as a value here, try printing json.Marshal(confmap)

Copy link
Contributor

@guillaumemichel guillaumemichel Feb 4, 2025

Choose a reason for hiding this comment

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

what do you mean?

I just think using the string "any" is strange. Would it work to test for a nil pointer or empty string instead? And adapting ReflectToMap accordingly.

Copy link
Author

Choose a reason for hiding this comment

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

There are other empty strings in the output of ReflectToMap. Nil pointer could work, but "any" is perhaps more expressive e. g. this key contain anything, stop validating. I can change it to nil ptr if you want to.

Copy link
Author

Choose a reason for hiding this comment

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

NVM, empty strings and nil values would also work

Copy link
Author

Choose a reason for hiding this comment

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

NVM, It wouldn't work with empty strings, but it would still fail at a later stage.

Copy link
Author

Choose a reason for hiding this comment

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

Changed "any" to nil

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.

Validate configuration
3 participants