-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): add warning on version mismatch between client and server. F…
…ixes #9212 This adds a warning message to the CLI when it detects a mismatch between the client and server versions. There was another PR (#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 <[email protected]>
- Loading branch information
Showing
7 changed files
with
180 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package grpc | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/metadata" | ||
) | ||
|
||
type mockServerTransportStream struct { | ||
header metadata.MD | ||
} | ||
|
||
func (mockServerTransportStream) Method() string { return "" } | ||
func (msts *mockServerTransportStream) SetHeader(md metadata.MD) error { | ||
msts.header = md | ||
return nil | ||
} | ||
func (mockServerTransportStream) SendHeader(md metadata.MD) error { return nil } | ||
func (mockServerTransportStream) SetTrailer(md metadata.MD) error { return nil } | ||
|
||
var _ grpc.ServerTransportStream = &mockServerTransportStream{} | ||
|
||
func TestSetVersionHeaderUnaryServerInterceptor(t *testing.T) { | ||
version := &wfv1.Version{Version: "v3.1.0"} | ||
handler := func(ctx context.Context, req interface{}) (interface{}, error) { return nil, nil } | ||
msts := &mockServerTransportStream{} | ||
ctx := grpc.NewContextWithServerTransportStream(context.Background(), msts) | ||
interceptor := SetVersionHeaderUnaryServerInterceptor(*version) | ||
|
||
_, err := interceptor(ctx, nil, &grpc.UnaryServerInfo{}, handler) | ||
|
||
require.NoError(t, err) | ||
assert.Equal(t, metadata.Pairs(ArgoVersionHeader, version.Version), msts.header) | ||
} | ||
|
||
type mockServerStream struct { | ||
header metadata.MD | ||
} | ||
|
||
func (msts mockServerStream) SetHeader(md metadata.MD) error { | ||
msts.header.Set(ArgoVersionHeader, md.Get(ArgoVersionHeader)...) | ||
return nil | ||
} | ||
func (mockServerStream) SendHeader(md metadata.MD) error { return nil } | ||
func (mockServerStream) SetTrailer(md metadata.MD) {} | ||
func (mockServerStream) Context() context.Context { return context.Background() } | ||
func (mockServerStream) SendMsg(m any) error { return nil } | ||
func (mockServerStream) RecvMsg(m any) error { return nil } | ||
|
||
var _ grpc.ServerStream = &mockServerStream{} | ||
|
||
func TestSetVersionHeaderStreamServerInterceptor(t *testing.T) { | ||
version := &wfv1.Version{Version: "v3.1.0"} | ||
handler := func(srv any, stream grpc.ServerStream) error { return nil } | ||
msts := &mockServerStream{header: metadata.New(nil)} | ||
interceptor := SetVersionHeaderStreamServerInterceptor(*version) | ||
|
||
err := interceptor(nil, msts, nil, handler) | ||
|
||
require.NoError(t, err) | ||
assert.Equal(t, metadata.Pairs(ArgoVersionHeader, version.Version), msts.header) | ||
} |