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

feat(cli): add version header + warning on client-server mismatch. Fixes #9212 #13635

Merged
merged 9 commits into from
Sep 30, 2024

Conversation

MasonM
Copy link
Contributor

@MasonM MasonM commented Sep 20, 2024

Fixes #9212

Motivation

This adds a warning message to the CLI when it detects a mismatch between the client and server versions.

Modifications

There was another PR (#11909) for this issue that implemented it by making a blocking API call to /api/v1/version in a PersistentPreRun hook. This PR takes a different approach: have the server send the version in a new header called argo-version, which the client will extract. There's several advantages to this approach:

  1. Negligible performance impact, since no additional requests are needed.
  2. Warning is only shown when the command would normally send an API request
  3. Can be useful for bug triaging, since the header can be seen in curl output:
    $ curl -v http://localhost:2746/api/v1/info
    * Host localhost:2746 was resolved.
    * IPv6: ::1
    * IPv4: 127.0.0.1
    *   Trying [::1]:2746...
    * connect to ::1 port 2746 from ::1 port 62973 failed: Connection refused
    *   Trying 127.0.0.1:2746...
    * Connected to localhost (127.0.0.1) port 2746
    > GET /api/v1/info HTTP/1.1
    > Host: localhost:2746
    > User-Agent: curl/8.8.0
    > Accept: */*
    >
    * Request completely sent off
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    < Grpc-Metadata-Argo-Version: latest+303bcce.dirty
    < Grpc-Metadata-Content-Type: application/grpc
    < X-Ratelimit-Limit: 1000
    < X-Ratelimit-Remaining: 999
    < X-Ratelimit-Reset: Fri, 20 Sep 2024 17:44:03 UTC
    < Date: Fri, 20 Sep 2024 17:44:02 GMT
    < Content-Length: 1392
    <
    * Connection #0 to host localhost left intact
    {"managedNamespace":"argo","links":[{"name":"Workflow Link","scope":"workflow","url":"http://logging-facility?namespace=${metadata.namespace}\u0026workflowName=${metadata.name}\u0026startedAt=${status.startedAt}\u0026finishedAt=${status.finishedAt}"},{"name":"Pod Link",
    "scope":"pod","url":"http://logging-facility?namespace=${metadata.namespace}\u0026podName=${metadata.name}\u0026startedAt=${status.startedAt}\u0026finishedAt=${status.finishedAt}"},{"name":"Pod Logs Link","scope":"pod-logs","url":"http://logging-facility?namespace=${met
    adata.namespace}\u0026podName=${metadata.name}\u0026startedAt=${status.startedAt}\u0026finishedAt=${status.finishedAt}"},{"name":"Event Source Logs Link","scope":"event-source-logs","url":"http://logging-facility?namespace=${metadata.namespace}\u0026podName=${metadata.n
    ame}\u0026startedAt=${status.startedAt}\u0026finishedAt=${status.finishedAt}"},{"name":"Sensor Logs Link","scope":"sensor-logs","url":"http://logging-facility?namespace=${metadata.namespace}\u0026podName=${metadata.name}\u0026startedAt=${status.startedAt}\u0026finishedA
    t=${status.finishedAt}"},{"name":"Completed Workflows","scope":"workflow-list","url":"http://workflows?label=workflows.argoproj.io/completed=true"}],"modals":{"feedback":true,"firstTimeUser":true,"newVersion":true},"columns":[{"name":"Workflow Completed","type":"label",
    "key":"workflows.argoproj.io/completed"}]}%
    

Exposing the version information has security implications, since it could be used by attackers to identify vulnerable Argo servers. To mitigate that, the header is not sent on 401 errors. Of course, if a user is exposing their Argo server to the internet without authentication, then an attacker could see this header, but then they've got bigger problems (and an attacker could just call /api/v1/version).

This is implemented on the client and server side using grpc-go interceptors. On the server side, there's interceptors to set the version header in the response. On the client side, there's an interceptor to check the response for the header and stash it in a global variable (which is obviously not ideal, but I couldn't think of a better way to handle that).

Verification

Testing process:

  1. Manually changed the version to v9.99 here:
    -X github.com/argoproj/argo-workflows/v3.version=$(VERSION) \
  2. Ran make cli && cp dist/argo argo2
  3. Ran git checkout Makefile && make start API=true
  4. Ran ARGO_SECURE=false ARGO_TOKEN="Bearer $(kubectl get secret argo-server.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)" ARGO_SERVER=localhost:2746 ./dist/argo2 list

Output:

No workflows found
WARN[2024-09-20T18:03:26.116Z] CLI version (v9.99+303bcce.dirty) does not match server version (latest+303bcce.dirty). This can lead to unexpected behavior.

…ixes argoproj#9212

This adds a warning message to the CLI when it detects a mismatch
between the client and server versions. There was another PR
(argoproj#11909) for this
implemented it by making a blocking API call to `/api/v1/version` in a
`PersistentPreRun` hook. This PR takes a different approach: have the
server send the version in a new header called `argo-version`, which the
client will detect and extract. There's several advantages to this
approach:
1. Negligible performance impact, since no additional requests are
   needed.
2. Warning is only shown when the command would normally send an API
   request.
3. Can be useful for bug triaging, since the header can be seen in
   `curl` output.

Exposing the version information has security implictions, since it
could be used by attackers to identify vulnerable Argo servers. To
mitigate that, the header is not sent on 401 errors. Of course, if
a user is exposing their Argo server to the internet without
authentication, then an attacker could see this header, but then they've
got bigger problems (and an attacker could just call `/api/v1/version`).

This is implemented on the client and server side using [grpc-go
interceptors](https://github.com/grpc/grpc-go/blob/master/examples/features/interceptor/README.md).
On the server side, there's interceptors to set the version header in
the response. On the client side, there's an interceptor to check the
response for the header and stash it in a global variable (which
is obviously not ideal, but I couldn't think of a better way to handle
that).

Testing process:
1. Manually changed the version to `v9.99`: https://github.com/argoproj/argo-workflows/blob/ce7f9bfb9b45f009b3e85fabe5e6410de23c7c5f/Makefile#L95
2. Ran `make cli && cp dist/argo argo2`
3. Ran `make start API=true`
4. Ran `ARGO_SECURE=false ARGO_TOKEN="Bearer $(kubectl get secret argo-server.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)" ARGO_SERVER=localhost:2746 ./dist/argo2 list`

Output:
```
No workflows found
WARN[2024-09-20T18:03:26.116Z] CLI version (v9.99+303bcce.dirty) does not match server version (latest+303bcce.dirty). This can lead to unexpected behavior.
```

Signed-off-by: Mason Malone <mmalone@adobe.com>
@MasonM MasonM marked this pull request as ready for review September 20, 2024 19:40
@MasonM
Copy link
Contributor Author

MasonM commented Sep 20, 2024

The one test failure is pretty clearly unrelated, and seems to be due to resource issues:

 ErrImageNeverPull: Container image "quay.io/argoproj/argocli:latest" is not present with pull policy of Never

    artifacts_test.go:218: timeout after 1m30s waiting for condition

@agilgur5 do you know how to selectively re-run that failed test?

@agilgur5 agilgur5 changed the title feat(cli): add warning on version mismatch between client and server. Fixes #9212 feat(cli): add version header + warning on client-server mismatch. Fixes #9212 Sep 20, 2024
Copy link
Member

@agilgur5 agilgur5 left a comment

Choose a reason for hiding this comment

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

I'm not necessarily opposed to this given that I mentioned using a header in #11909 (comment), but modifying gRPC a decent bit here seems suboptimal given #13542

util/cmd/cmd_test.go Outdated Show resolved Hide resolved
util/grpc/interceptor.go Show resolved Hide resolved
@agilgur5
Copy link
Member

agilgur5 commented Sep 20, 2024

@agilgur5 do you know how to selectively re-run that failed test?

In CI, you can't. You can re-run an individual CI job with a /retest comment after #13000 et al, but you need to be an Argoproj Member to do so. Other Members can add that comment for you and Approvers can manually re-run CI jobs as well.

Speaking of, would you be interested in becoming a Member? You have enough contributions to qualify now and enough would merge by the next quarterly Membership Meeting (November) as well

Signed-off-by: Mason Malone <mmalone@adobe.com>
@MasonM
Copy link
Contributor Author

MasonM commented Sep 21, 2024

@agilgur5 Thanks!

Yes, I'm interested in becoming a member, but I need to get sign off from my employer first. Are you willing to sponsor me?

@agilgur5
Copy link
Member

Yes I mentioned it, so ofc I can be one sponsor 🙂
Alan up-voted my comment and is potentially another sponsor, but we'll definitely be able to get you a second or more too.

You mentioned you were contributing in your personal capacity, so you don't have to list your employer as your affiliation, but it is usually good to cross-check with them anyway. Pretty sure Adobe would be happy to have more contributors affiliated too 😉

util/grpc/interceptor.go Outdated Show resolved Hide resolved
Copy link
Member

@agilgur5 agilgur5 left a comment

Choose a reason for hiding this comment

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

Awesome work with all the grpc-go interceptor nuances here, I learned some things from your code 🙂
The detailed PR and tests are great too ❤️

I just have two non-blocking comments that we may or may not want to act further on

util/grpc/interceptor.go Outdated Show resolved Hide resolved
util/cmd/cmd.go Show resolved Hide resolved
@MasonM
Copy link
Contributor Author

MasonM commented Sep 25, 2024

Test failures are unrelated and should go away when #13660 is merged

@agilgur5 agilgur5 added this to the v3.6.0 milestone Sep 26, 2024
util/grpc/interceptor.go Outdated Show resolved Hide resolved
Signed-off-by: Mason Malone <mmalone@adobe.com>
Signed-off-by: Mason Malone <mmalone@adobe.com>
Signed-off-by: Mason Malone <mmalone@adobe.com>
Copy link
Member

@agilgur5 agilgur5 left a comment

Choose a reason for hiding this comment

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

Thanks for iterating on this and being careful of the possible security ramifications!

@agilgur5 agilgur5 enabled auto-merge (squash) September 30, 2024 16:21
@agilgur5 agilgur5 merged commit 54621cc into argoproj:main Sep 30, 2024
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/api Argo Server API area/cli The `argo` CLI type/security Security related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CLI should warn when CLI version is outdated
3 participants