-
Notifications
You must be signed in to change notification settings - Fork 231
OCM-18900 | fix: Improve proxy validation error messages for special characters in passwords #3052
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3052 +/- ##
==========================================
+ Coverage 27.91% 28.07% +0.15%
==========================================
Files 316 316
Lines 35008 35043 +35
==========================================
+ Hits 9774 9837 +63
+ Misses 24582 24551 -31
- Partials 652 655 +3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
fc09c54 to
aa528ca
Compare
00638f4 to
120a803
Compare
|
/retest-required |
2260456 to
c6b94b4
Compare
|
/retest-required |
|
/retest-required |
hunterkepley
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test case in the bug ticket does not seem to pass:
E: parse "http://proxyuser:QvoZjyy/[email protected]:8080": invalid port ":QvoZjyy" after host
I think there may be a bug in the parse code
|
@hunterkepley I just did some tests here, and it seems to be working after the fix, did you test it with the branch locally ? |
96029c9 to
a50904c
Compare
|
/retest-required |
1 similar comment
|
/retest-required |
9502aa4 to
2637639
Compare
|
@hunterkepley My bad then, I understood this more as as a suggestions that a thing to initiate a discussion. Answering what you said, I think we can show the password, because this way the user can just do a quick comparison and figure out what's wrong, WDYT ? I'm also working on the test cases you commented |
dca6669 to
012f289
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: olucasfreitas The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
560e392 to
3df4265
Compare
|
/test all |
|
/test e2e-presubmits-pr-rosa-sts-advanced |
|
/test e2e-presubmits-pr-rosa-hcp-advanced |
|
Some reason the e2e tests didn't run, I manually ran them so we can verify this MR didn't break any |
3df4265 to
055b821
Compare
|
/retest |
2 similar comments
|
/retest |
|
/retest |
pkg/interactive/validation.go
Outdated
| if parsedUri == nil { | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've left 2 comments now asking about this 🤔 ; but why did you add this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added that because _isUrl can return nil when it gets an empty string or nil input, and without that check we'd maybe get a panic, so I wanted to be sure, sorry about this, as I got a lot of comments I must have let this one pass, just removed it on the last commit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@olucasfreitas Maybe it would be helpful to respond + resolve to comments one by one 🙂
But, I feel like something was misunderstood about the function based on the implementation of the nil check here
Here is the function you added this to:
func IsURLHttps(val interface{}) error {
parsedUri, err := _isUrl(val)
if err != nil {
return err
}
if parsedUri.Scheme != helper.ProtocolHttps {
return fmt.Errorf("expect URL '%s' to use an 'https://' scheme", val.(string))
}
return nil
}So, the point of this, is to return an error when it's not valid, and return nil when it is valid.
Lets review where this function is called:
issuerUrl, err := interactive.GetString(interactive.Input{
Question: "Issuer URL (please include 'https://')",
Help: cmd.Flags().Lookup(IssuerUrlFlag).Usage,
Required: true,
Validators: []interactive.Validator{interactive.IsURLHttps},
})Here it is used as a validator. The value is Required, meaning we cannot pass in an empty string.
The second place it's called is when validating another interactive prompt, but in a different way
If we look at the implementation of _isUrl, we can see the following:
func _isUrl(val interface{}) (*url.URL, error) {
if val == nil {
return nil, nil
}
s, ok := val.(string)
if !ok {
return nil, fmt.Errorf("can only validate strings, got %v", val)
}
if s == "" {
return nil, nil
}
parsedUri, err := url.ParseRequestURI(fmt.Sprintf("%v", val))
return parsedUri, err
}So, which cases does this return nil?
- When the URL passed in is nil (impossible for strings! it's possible with pointers to strings, but not strings)
- When the URL is empty (not possible with the shown implementation)
- When there is a non-string passed in (this will be caught by the function already, and returned, before it checks if it's nil, due to it returning an actual error)
All implementations have been around since 2023, and are not related to this feature 🙂 so we should leave those functions alone. They only deal with OIDC providers, not create/cluster like this ticket is supposed to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, I feel like something was misunderstood about the function based on the implementation of the nil check here
To circle it back to my original statement; If the URL was nil, I don't think we should return nil as the error. I think we should inform that the URL passed in was nil, and therefore incorrect. But again, we don't need any of it for this feature 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about the comments, I had understood that the person that commented would resolve the comments if in the next review they saw that the issues we're resolved
also sorry about not getting this at the first place, thanks for the run down, I think my last commit is now correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about the comments, I had understood that the person that commented would resolve the comments if in the next review they saw that the issues we're resolved
I was replying to your original reply: as I got a lot of comments I must have let this one pass, just removed it on the last commit
it's all good if you leave the comments for the reviewer to resolve, in fact that's a good idea. What I was getting at was that it may be helpful to take them one at a time, and reply to it after fixing it. I see usually that you respond to all of them after fixing everything, then post the commit along with the replies is the reason I suggested it 🙂 might help break up the context of each review comment some
|
/test e2e-presubmits-pr-rosa-hcp-advanced |
|
/test e2e-presubmits-pr-rosa-sts-advanced |
34c6edf to
875a547
Compare
|
@olucasfreitas: The following test failed, say
Full PR test history. Your PR dashboard. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
|
/retest |
875a547 to
4fe55d7
Compare
4fe55d7 to
a51e1b8
Compare
| Field string | ||
| InvalidChar rune | ||
| HasInvalid bool | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should define structs at the top of files to be consistent with other files
I also suggest maybe putting this + the function below into a more specific file for its purpose (maybe pkg/helpers/url/validation.go would suite this nicely, or some variation of this)
| return &URLCredentialValidation{ | ||
| Field: field, | ||
| InvalidChar: char, | ||
| HasInvalid: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could drop this field, and instead just check if InvalidChar is an empty rune or not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT?
| return checkForInvalidChars(password, "password") | ||
| } | ||
|
|
||
| func checkForInvalidChars(value, field string) *URLCredentialValidation { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should also go in that new file dedicated to validating URLs
| HasInvalid bool | ||
| } | ||
|
|
||
| func ValidateURLCredentials(urlString string) *URLCredentialValidation { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same for this
|
|
||
| schemeIdx := strings.Index(urlString, "://") | ||
| if schemeIdx == -1 { | ||
| return &URLCredentialValidation{HasInvalid: false} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably tell the user what is wrong here, right? This only says there is an invalid character somewhere
| if parsedURL.Scheme == "" { | ||
| return fmt.Errorf("invalid %s value: URL scheme is missing (e.g., %s://)", proxyType, expectedScheme) | ||
| } | ||
| if parsedURL.Scheme != expectedScheme { | ||
| return errors.Errorf("%s", fmt.Sprintf("expected %s to have an %s:// scheme", proxyType, expectedScheme)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should encase the %s's in single quotes, and get rid of this first "empty check" validation. They are one and the same if we use single quotes. WDYT? Does that make sense?
|
|
||
| // Proxy URLs shouldn't have a path | ||
| if parsedURL.Path != "" && parsedURL.Path != "/" { | ||
| return fmt.Errorf("invalid %s value: proxy URL should not contain a path", proxyType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should let the user know what the value was in case they are confused?
| host, port, err := net.SplitHostPort(parsedURL.Host) | ||
| if err != nil { | ||
| // No port specified, just validate the host | ||
| host = parsedURL.Host |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure that the only reason this can return an error is due to not having a port specified? I would suggest looking at the source code for that net pkg func
| portNum, _ := strconv.Atoi(port) | ||
| if portNum < 1 || portNum > 65535 { | ||
| return fmt.Errorf("invalid %s value: port must be between 1 and 65535", proxyType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this validation specifically- the user shouldn't be inputting a giant or negative port number if they have a proxy URL they know they want to use. Having it result in a 404 eventually would be fine UX. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though there's no harm in keeping it specifically, it just strays some from the original goal
| // Not an IP, check if it's a valid hostname | ||
| if strings.Contains(host, "/") { | ||
| return fmt.Errorf("invalid %s value: invalid host", proxyType) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this needed? How do we know it was a slash that caused this? What happens if it wasn't a slash?
Details
This PR fixes a usability issue where
rosa create cluster --http-proxywas displaying unhelpful error messages when proxy passwords contained special characters like forward slashes, making it difficult for users to understand what was wrong with their input.Fixed Behavior
Run the command with a proxy password containing a forward slash:
./rosa create cluster --http-proxy "http://proxyuser:QvoZjyy/[email protected]:8080"Now shows clear error message:
Additional Validations Added
Spaces in URL - Detects and rejects URLs with spaces:
./rosa create cluster --http-proxy "http://proxy.example.com :8080"Output:
Invalid Port Range - Validates port numbers are between 1 and 65535:
./rosa create cluster --http-proxy "http://proxy.example.com:99999"Output:
Multiple @ Symbols - Detects unencoded @ in passwords:
./rosa create cluster --http-proxy "http://user:pass@[email protected]:8080"Output:
The --https-proxy Flag Behavior
Using HTTPS scheme with
--http-proxycontinues to show the existing error:./rosa create cluster --http-proxy "https://proxy.example.com:8080"Output:
Ticket
Closes OCM-18900